管道提供单向数据流,fd[0]读,fd[1]写
创建全双工IPC管道的方法是 socketpair().
由于管道是字符流,所以需要用 分隔符 或 格式化的 方式 避免粘包
#include <linux/limits.h> #include <sys/uio.h> #include <signal.h> #include <errno.h> #include <fcntl.h> #include <sys/stat.h> #include <string.h> #include <stdio.h> #include <limits.h> #include <stdlib.h> #include <sys/wait.h> #include <sys/types.h> #include <unistd.h> typedef struct msg_hdr { int len; } msg_hdr_t; int msg_read(int fd, char *buf, int buf_size) { msg_hdr_t msg_hdr; int nbytes; nbytes = read(fd, &msg_hdr, sizeof(msg_hdr_t)); if (nbytes < 0) { perror("read"); return -1; } if (nbytes == 0) { // 管道写端全关闭,说明对方已经停止会话 return -2; } if (msg_hdr.len == 0) { // 一次 单向通信 结束 return 0; } nbytes = read(fd, buf, msg_hdr.len); if (nbytes < 0) { return -1; } if (nbytes != msg_hdr.len) { return -1; } return msg_hdr.len; } void client(int readfd, int writefd) { char buf[LINE_MAX]; // buf 大小设置为LINE_MAX,让程序不需要担心输入限制 int len, nbytes; while (fgets(buf, sizeof(buf), stdin)) { // 行输入, 用户输入EOF,退出 len = strlen(buf); if (len > PIPE_BUF) { // 若输入长度大于 PIPE_BUF,导致pipe不能保证原子性 printf("filename is too long\n"); continue; } if (buf[0] == '\n') { continue; } nbytes = write(writefd, buf, len); // 输入的数据以\n为分隔符 if (nbytes < 0) { perror("write"); continue; } if (nbytes != len) { // 因为保证了输入长度小于PIPE_BUF,所以pipe必须保证输入的原子性,所以nbytes != len 是不应该的 printf("error in pipe write\n"); } while ((nbytes = msg_read(readfd, buf, sizeof(buf))) > 0) { fwrite(buf, 1, nbytes, stdout); } if (nbytes == -1) { perror("msg_read error\n"); continue; } if (nbytes == -2) { break; } } printf("client quit\n"); close(readfd); // 向对方发送 EOF close(writefd); return; } int msg_send(int fd, char *buf, int n) { char *p; int len, max; msg_hdr_t msg; max = PIPE_BUF - sizeof(msg_hdr_t); // pipe只保证 数据大小小于 PIPE_BUF 的原子性,为了保证 数据报的完整,必须 小于等于 PIPE_BUF p = buf; if (n == 0 || buf == NULL) { msg.len = 0; if (write(fd, &msg, sizeof(msg)) < 0) { perror("write"); return -1; } return 0; } while (n > 0) { len = n > max ? max : n; msg.len = len; struct iovec iov[2]; // 向量IO能保证原子性,并避免多余的内存消耗 iov[0].iov_base = &msg; iov[0].iov_len= sizeof(msg); iov[1].iov_base = p; iov[1].iov_len = msg.len; if (writev(fd, iov, sizeof(iov)/sizeof(*iov)) < 0) { perror("writev"); return -1; } n -= len; p += len; } return 0; } void server(int readfd, int writefd) { FILE *fp; int fd, nbytes; char buf[LINE_MAX]; fp = fdopen(readfd, "r"); // readfd的数据流,使用\n作为分隔符,所以利用标准IO的行缓存机制 if (fp == NULL) { perror("fdopen"); return; } while (fgets(buf, sizeof(buf), fp)) { // 以\n为分隔符 char *ptr = strchr(buf, '\n'); // 去掉分隔符 *ptr = '\0'; fd = open(buf, O_RDONLY); if (fd < 0) { // 若错误,则返回报错信息 strcpy(buf, strerror(errno)); if (msg_send(writefd, buf, strlen(buf)) < 0) { perror("server write"); break; } msg_send(writefd, NULL, 0); continue; } while ((nbytes = read(fd, buf, sizeof(buf))) > 0) { msg_send(writefd, buf, nbytes); // 报文头显示说明数据长度 } if (nbytes < 0) { strcpy(buf, strerror(errno)); if (msg_send(writefd, buf, strlen(buf)) < 0) { perror("server write"); break; } msg_send(writefd, NULL, 0); } msg_send(writefd, NULL, 0); close(fd); } printf("server quit\n"); close(writefd); close(fd); fclose(fp); } int main() { pid_t pid; int fd[4]; if ((pipe(fd) < 0) || (pipe(fd + 2) < 0)) { perror("pipe"); return -1; } signal(SIGPIPE, SIG_IGN); // 写 无读端 的pipe 导致 SIGPIPE, SIGPIPE 默认动作为终止进程,忽略该信号,让write返回 EPIPE pid = fork(); switch (pid) { case 0: close(fd[2]); close(fd[1]); // 必须把 不需要的描述符关闭,否则 关闭 写端,对方无法受到 EOF client(fd[0], fd[3]); exit(0); break; case -1: break; default: close(fd[0]); close(fd[3]); server(fd[2], fd[1]); } wait(NULL); return 0; }