说到匿名管道(pipe)和命名管道(fifo),更偏进于进程间通信,不是进程内线程通信的主力。
但在实际项目中也非常常见。
实际项目高频场景,如下方描述
比如主程序启动一个外部工具进程,用它去做某些事情:
主进程发命令给子进程
子进程把执行结果返回给主进程
这时通常用pipe.
比如后台守护进程一直运行,工程师有时需要通过一个命令行小工具给它发命令:
reload、dump_status、enable_debug
两个进程互不相关,这时常用FIFO.
Pipe(匿名管道)
只能用于有亲缘关系的进程,如:父子进程
pipe(fd)创建后,配合(fork)使用
半双工,一端读一端写
FIFO(命名管道)
本质也是管道
但它在文件系统里有名字
可以给无亲缘关系的进程使用
头文件:
#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>Pipe
int pipe(int fd[2]);fd[0]: 读端
fd[1]: 写端
FIFO
int mkfifo(const char *pathname,mode_t mode);配合使用:
open()、read()、write()、close()
通俗理解:
你可以把pipe/FIFO看成一个”内核帮你保管的传送带“:
写端往传送带放数据
读端往传送带拿数据
特点是:
按顺序传输
是字节流
不关心你写的是”消息1“还是”消息2“
更像文件读写
场景:父进程向子进程发送一个"采集状态查询命令", 子进程返回结果。
vim 02_pipe_fifo.c
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <sys/wait.h>/* * 这里做双向通信: * parent -> child : 发送命令 * child -> parent: 返回结果 * * 所以用了两个pipe */int main(void){ int pipe_cmd[2]; // 父发命令给子 int pipe_resp[2]; // 子返回结果给父if (pipe(pipe_cmd) == -1 || pipe(pipe_resp) == -1) { perror("pipe");return 1; } pid_t pid = fork();if (pid < 0) { perror("fork");return 1; }if (pid == 0) { /* 子进程 */ close(pipe_cmd[1]); // 子进程不用命令写端 close(pipe_resp[0]); // 子进程不用响应读端 char cmd[128] = {0};read(pipe_cmd[0], cmd, sizeof(cmd));printf("[child] received cmd: %s\n", cmd); /* * 模拟执行命令 * 实际项目中可能是查询设备状态、读取传感器值等 */ char resp[128] = {0};if (strcmp(cmd, "GET_STATUS") == 0) { snprintf(resp, sizeof(resp), "status=OK,temp=42"); } else { snprintf(resp, sizeof(resp), "unknown command"); } write(pipe_resp[1], resp, strlen(resp) + 1); close(pipe_cmd[0]); close(pipe_resp[1]);exit(0); } else { /* 父进程 */ close(pipe_cmd[0]); // 父进程不用命令读端 close(pipe_resp[1]); // 父进程不用响应写端 const char *cmd = "GET_STATUS"; write(pipe_cmd[1], cmd, strlen(cmd) + 1); char resp[128] = {0};read(pipe_resp[0], resp, sizeof(resp));printf("[parent] child response: %s\n", resp); close(pipe_cmd[1]); close(pipe_resp[0]);wait(NULL); }return 0;}编译运行:
tammy@ubuntu:~/cprogramer/thread_pro$ gcc 02_pipe_fifo.c -o 02_pipe_fifo -lpthreadtammy@ubuntu:~/cprogramer/thread_pro$ ./02_pipe_fifo[child] received cmd: GET_STATUS[parent] child response: status=OK,temp=42场景:后台服务通过/tmp/device_cmd_fifo接收外部调试命令。
服务端:server_fifo.c
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#define FIFO_PATH "/tmp/device_cmd_fifo"int main(void){ /* 创建FIFO,如果已经存在也没关系 */if (mkfifo(FIFO_PATH, 0666) == -1) { perror("mkfifo maybe already exists"); }printf("[server] waiting command on %s ...\n", FIFO_PATH);while (1) { /* * O_RDONLY 打开会阻塞,直到有写端连接 * 适合服务端一直等待客户端命令 */ int fd = open(FIFO_PATH, O_RDONLY);if (fd < 0) { perror("open");continue; } char buf[128] = {0}; int n = read(fd, buf, sizeof(buf) - 1);if (n > 0) {printf("[server] received cmd: %s\n", buf);if (strcmp(buf, "reload") == 0) {printf("[server] do reload config ...\n"); } elseif (strcmp(buf, "dump_status") == 0) {printf("[server] dump current status ...\n"); } else {printf("[server] unknown cmd\n"); } } close(fd); }return 0;}客户端:client_fifo.c
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#define FIFO_PATH "/tmp/device_cmd_fifo"int main(int argc, char *argv[]){if (argc != 2) {printf("usage: %s <cmd>\n", argv[0]);printf("example: %s reload\n", argv[0]);return 1; } int fd = open(FIFO_PATH, O_WRONLY);if (fd < 0) { perror("open");return 1; } write(fd, argv[1], strlen(argv[1]) + 1); close(fd);return 0;}编译:
tammy@ubuntu:~/cprogramer/thread_pro$ gcc 03_server_fifo.c -o 03_server_fifotammy@ubuntu:~/cprogramer/thread_pro$ gcc 03_client_fifo.c -o 03_client_fifotammy@ubuntu:~/cprogramer/thread_pro$ ls01_mutex_cond.c 03_client_fifo 03_server_fifo02_pipe_fifo.c 03_client_fifo.c 03_server_fifo.c运行终端1(server程序):
tammy@ubuntu:~/cprogramer/thread_pro$ ./03_server_fifomkfifo maybe already exists: File exists[server] waiting command on /tmp/device_cmd_fifo ...[server] received cmd: reload[server] do reload config ...[server] received cmd: dump_status[server] dump current status ...运行 终端2(client程序):
tammy@ubuntu:~/cprogramer/thread_pro$ ./03_client_fifo reloadtammy@ubuntu:~/cprogramer/thread_pro$ ./03_client_fifo dump_statusPipe 适合
父子进程
主程序拉起工具进程
shell 风格串联处理
FIFO 适合
后台守护进程接收简单命令
测试工具向服务发送指令
无关进程间的轻量文本通信
注意
它是字节流
不适合复杂结构化协议
如果消息复杂,通常会自己设计协议头,或者直接用消息队列/socket