命名管道是一种特殊的文件类型,允许无亲缘关系的进程之间进行通信。与匿名管道(pipe)不同,FIFO在文件系统中有一个真实的文件名,任何知道该文件名的进程都可以访问它。
1. 命令行创建
# 创建命名管道
mkfifo /tmp/myfifo
# 查看文件类型
ls -l /tmp/myfifo
# 输出:prw-r--r-- 1 user user 0 Jan 1 12:00 /tmp/myfifo
# 'p' 表示管道文件
2. 程序内创建
#include <sys/types.h>
#include <sys/stat.h>
// 创建FIFO,mode为权限(如0666)
int mkfifo(const char *pathname, mode_t mode);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FIFO_FILE "/tmp/myfifo"
#define BUFFER_SIZE 1024
int main() {
int fd;
char buff[BUFFER_SIZE];
// 创建FIFO(如果不存在)
if (mkfifo(FIFO_FILE, 0666) == -1) {
// 可能已存在,继续尝试打开
}
printf("生产者启动,等待消费者连接...\n");
// 打开FIFO进行写入(会阻塞直到消费者打开读取端)
fd = open(FIFO_FILE, O_WRONLY);
while (1) {
printf("输入消息 (输入'quit'退出): ");
fgets(buff, BUFFER_SIZE, stdin);
// 写入FIFO
write(fd, buff, strlen(buff)+1);
if (strncmp(buff, "quit", 4) == 0) {
break;
}
}
close(fd);
// unlink(FIFO_FILE); // 可选:删除FIFO文件
return 0;
}
消费者程序(reader.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FIFO_FILE "/tmp/myfifo"
#define BUFFER_SIZE 1024
int main() {
int fd;
char buff[BUFFER_SIZE];
printf("消费者启动,等待数据...\n");
// 打开FIFO进行读取(会阻塞直到生产者打开写入端)
fd = open(FIFO_FILE, O_RDONLY);
while (1) {
// 从FIFO读取数据
int bytes_read = read(fd, buff, BUFFER_SIZE);
if (bytes_read > 0) {
printf("收到消息: %s", buff);
if (strncmp(buff, "quit", 4) == 0) {
break;
}
}
}
close(fd);
return 0;
}
# 编译
gcc writer.c -o writer
gcc reader.c -o reader
# 终端1:运行消费者
./reader
# 终端2:运行生产者
./writer
// 非阻塞方式打开FIFO
int fd = open(FIFO_FILE, O_RDONLY | O_NONBLOCK);
// 或通过fcntl设置
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
2. 多客户端服务器模型
// 服务器处理多个客户端
#include <errno.h>
void server_handle_multiple_clients() {
int fd, bytes;
char buffer[256];
mkfifo("/tmp/server_fifo", 0666);
while (1) {
// 打开服务器FIFO(阻塞等待客户端)
fd = open("/tmp/server_fifo", O_RDONLY);
// 读取客户端请求
while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) {
printf("收到请求: %s\n", buffer);
// 创建唯一的响应FIFO名
char response_fifo[50];
sprintf(response_fifo, "/tmp/client_%s_fifo", buffer);
// 打开客户端FIFO并发送响应
int resp_fd = open(response_fifo, O_WRONLY);
write(resp_fd, "响应数据", 10);
close(resp_fd);
}
close(fd);
}
}
3. 双向通信实现
// 进程A创建两个FIFO
mkfifo("/tmp/AtoB", 0666);
mkfifo("/tmp/BtoA", 0666);
// 进程A:向AtoB写,从BtoA读
// 进程B:向BtoA写,从AtoB读
容量限制:Linux默认64KB(可配置)
原子性:写入数据量≤PIPE_BUF(通常4096字节)时保证原子性
阻塞问题:
清理问题:FIFO文件需要手动删除
// 删除FIFO文件
unlink("/tmp/myfifo");
| 特性 | 命名管道 | 匿名管道 | 消息队列 | 共享内存 |
|---|---|---|---|---|
| 无亲缘关系 | ✅ | ❌ | ✅ | ✅ |
| 文件系统可见 | ✅ | ❌ | ❌ | ❌ |
| 通信方向 | 半双工 | 半双工 | 全双工 | 全双工 |
| 性能 | 中 | 高 | 中 | 高 |
| 数据格式 | 字节流 | 字节流 | 消息 | 字节流 |
# 终端1:创建并写入FIFO
mkfifo /tmp/myfifo
echo "Hello World" > /tmp/myfifo
终端2:从FIFO读取
cat < /tmp/myfifo
2. **日志收集系统**
```c
// 多个进程向同一个FIFO写入日志
// 日志收集进程从FIFO读取并统一处理
进程池任务分发// 主进程向FIFO写入任务
// 工作进程从FIFO读取并执行
// 使用poll实现超时读取
struct pollfd fds[1];
fds[0].fd = fd;
fds[0].events = POLLIN;
int ret = poll(fds, 1, 3000); // 3秒超时
if (ret > 0 && (fds[0].revents & POLLIN)) {
read(fd, buffer, size);
}
命名管道作为一种简单有效的进程间通信机制,特别适用于单向数据流和无亲缘关系进程的通信场景。虽然功能相对简单,但在许多系统编程场景中仍然非常实用。