管道基本概念:
管道是Unix中最古老的进程间通信形式
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
管道的本质:固定大小的内核缓冲区
管道限制:
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后,父子进程之间就可以应用该管道。
匿名管道pipe:用在父子进程之间或有血缘关系的进程之间
原型:int pipe(int fd[2])
功能:创建一个无名管道
参数:
fd:文件描述符数组,其中,fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误码
管道创建后示意图:
管道的读写规则如下:
管道读写示例:
1 #include <unistd.h> 2 #include <sys/stat.h> 3 #include <sys/wait.h> 4 #include <sys/types.h> 5 #include <fcntl.h> 6 7 #include <stdlib.h> 8 #include <stdio.h> 9 #include <errno.h> 10 #include <string.h> 11 #include <signal.h> 12 13 #define ERR_EXIT(m) \ 14 do \ 15 { \ 16 perror(m); \ 17 exit(EXIT_FAILURE); \ 18 } while(0) 19 20 21 //测试 默认情况下,管道的读写 22 int main41(void ) 23 { 24 int pipefd[2]; 25 pid_t pid; 26 if (pipe(pipefd) == -1 ) 27 { 28 printf("pipe() err..\n"); 29 return -1; 30 } 31 pid = fork(); 32 if (pid == -1) 33 { 34 printf("fork err..\n"); 35 return -1; 36 } 37 38 if (pid == 0) 39 { 40 printf("子进程延迟3秒后,再写。。\n"); 41 sleep(3); 42 close(pipefd[0]); 43 write(pipefd[1], "hello hello....", 6); 44 close(pipefd[1]); 45 printf("child .....quit\n"); 46 exit(0); 47 } 48 else if (pid > 0 ) 49 { 50 int len = 0; 51 char buf[100] = {0}; 52 close(pipefd[1]); 53 54 55 printf("测试非阻塞情况下,begin read ...\n"); 56 len = -1; 57 while (len < 0) 58 { 59 len = read(pipefd[0], buf, 100); 60 if (len == -1) 61 { 62 sleep(1); 63 //close(pipefd[0]); 64 perror("\nread err:"); 65 //exit(0); 66 } 67 } 68 69 printf("len:%d, buf:%s \n", len , buf); 70 close(pipefd[0]); 71 } 72 73 wait(NULL); 74 printf("parent ..quit\n"); 75 return 0; 76 77 } 78 79 80 //文件状态设置成阻塞非阻塞 81 int main(void ) 82 { 83 int pipefd[2]; 84 pid_t pid; 85 if (pipe(pipefd) == -1 ) 86 { 87 printf("pipe() err..\n"); 88 return -1; 89 } 90 pid = fork(); 91 if (pid == -1) 92 { 93 printf("fork err..\n"); 94 return -1; 95 } 96 97 if (pid == 0) 98 { 99 sleep(3); 100 close(pipefd[0]); 101 write(pipefd[1], "hello hello....", 6); 102 close(pipefd[1]); 103 printf("child .....quit\n"); 104 } 105 else if (pid > 0 ) 106 { 107 int len = 0; 108 char buf[100] = {0}; 109 close(pipefd[1]); 110 111 /* 112 int fcntl(int fd, int cmd); 113 int fcntl(int fd, int cmd, long arg); 114 int fcntl(int fd, int cmd, struct flock *lock); 115 F_GETFL F_SETFL O_NONBLOCK 116 //注意不要设置错误成F_GETFD F_SETFD 117 118 119 */ 120 int flags = fcntl(pipefd[0], F_GETFL); 121 flags = flags | O_NONBLOCK; 122 int ret = fcntl(pipefd[0], F_SETFL, flags); 123 if (ret == -1) 124 { 125 printf("fcntl err.\n"); 126 exit(0); 127 } 128 129 //把pipefd[0]文件描述符修改成非阻塞 man fcntl 130 printf("begin read ...\n"); 131 len = -1; 132 while (len < 0) 133 { 134 len = read(pipefd[0], buf, 100); 135 if (len == -1) 136 { 137 sleep(1); 138 //close(pipefd[0]); 139 perror("read err.\n"); 140 //exit(0); 141 } 142 } 143 144 printf("len:%d, buf:%s \n", len , buf); 145 close(pipefd[0]); 146 } 147 148 wait(NULL); 149 printf("parent ..quit\n"); 150 return 0; 151 152 }
测试管道容量的程序:2.6.11内核之后默认是65536字节
1 #include <unistd.h> 2 #include <sys/stat.h> 3 #include <sys/wait.h> 4 #include <sys/types.h> 5 #include <fcntl.h> 6 7 #include <stdlib.h> 8 #include <stdio.h> 9 #include <errno.h> 10 #include <string.h> 11 #include <signal.h> 12 //测试管道容量 13 14 #define ERR_EXIT(m) \ 15 do \ 16 { \ 17 perror(m); \ 18 exit(EXIT_FAILURE); \ 19 } while(0) 20 21 22 void myhandle(int sig) 23 { 24 printf("recv sig:%d \n", sig); 25 } 26 27 28 int main(void ) 29 { 30 int pipefd[2]; 31 pid_t pid; 32 33 //注册管道处理函数 34 signal(SIGPIPE, myhandle); 35 36 if (pipe(pipefd) == -1 ) 37 { 38 printf("pipe() err..\n"); 39 return -1; 40 } 41 42 pid = fork(); 43 if (pid == -1) 44 { 45 printf("fork err..\n"); 46 return -1; 47 } 48 49 if (pid == 0) 50 { 51 int count = 0; 52 int ret = 0; 53 close(pipefd[0]); 54 55 56 //写端变成非阻塞 57 int flags = fcntl(pipefd[1], F_GETFL); 58 flags = flags | O_NONBLOCK; 59 ret = fcntl(pipefd[1], F_SETFL, flags); 60 if (ret == -1) 61 { 62 printf("fcntl err.\n"); 63 exit(0); 64 } 65 66 while(1) 67 { 68 ret = write(pipefd[1] , "a", 1); 69 if (ret == -1) 70 { 71 perror("write pipe"); 72 break; 73 } 74 count ++; 75 } 76 77 printf("count:%d \n\n", count); 78 close(pipefd[1]); 79 80 exit(0); 81 } 82 else if (pid > 0 ) 83 { 84 85 sleep(4); 86 close(pipefd[0]); 87 close(pipefd[1]); 88 } 89 90 wait(NULL); 91 printf("parent ..quit\n"); 92 return 0; 93 94 }
测试管道写入的PIPE_BUF原子性:原理是两个进程向管道里面写,一个进程读,看读出的数据是否乱序
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <errno.h> 7 #include <fcntl.h> 8 9 10 #define ERR_EXIT(m) \ 11 do \ 12 { \ 13 perror(m); \ 14 exit(EXIT_FAILURE); \ 15 } while(0) 16 17 #define TEST_SIZE 68*1024 //68K 18 19 20 int main(void) 21 { 22 char a[TEST_SIZE]; 23 char b[TEST_SIZE]; 24 25 memset(a, 'A', sizeof(a)); 26 memset(b, 'B', sizeof(b)); 27 28 int pipefd[2]; 29 30 int ret = pipe(pipefd); 31 if (ret == -1) 32 ERR_EXIT("pipe error"); 33 34 pid_t pid; 35 pid = fork(); 36 if (pid == 0) //A子进程写68K数据A 37 { 38 close(pipefd[0]); 39 ret = write(pipefd[1], a, sizeof(a)); 40 printf("apid=%d write %d bytes to pipe\n", getpid(), ret); 41 exit(0); 42 } 43 44 pid = fork(); 45 46 47 if (pid == 0) //B子进程写68K数据B 48 { 49 close(pipefd[0]); 50 ret = write(pipefd[1], b, sizeof(b)); 51 printf("bpid=%d write %d bytes to pipe\n", getpid(), ret); 52 exit(0); 53 } 54 55 56 close(pipefd[1]); 57 58 sleep(1); 59 int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); 60 char buf[1024*4] = {0}; 61 int n = 1; 62 while (1) 63 { 64 //父进程4k 4k的读数据,发现AB进程是交叉的写数据到管道。 65 //多个进程往管道里面,写数据。 66 ret = read(pipefd[0], buf, sizeof(buf)); 67 if (ret == 0) 68 break; 69 printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n", n++, getpid(), ret, buf[4095]); 70 write(fd, buf, ret); 71 72 } 73 return 0; 74 }
自己实现管道:
1 #include <unistd.h> 2 #include <sys/stat.h> 3 #include <sys/wait.h> 4 #include <sys/types.h> 5 #include <fcntl.h> 6 7 #include <stdlib.h> 8 #include <stdio.h> 9 #include <errno.h> 10 #include <string.h> 11 #include <signal.h> 12 13 #define ERR_EXIT(m) \ 14 do \ 15 { \ 16 perror(m); \ 17 exit(EXIT_FAILURE); \ 18 } while(0) 19 20 int main21(void ) 21 { 22 int pipefd[2]; 23 pid_t pid; 24 if (pipe(pipefd) == -1 ) 25 { 26 printf("pipe() err..\n"); 27 return -1; 28 } 29 pid = fork(); 30 if (pid == -1) 31 { 32 printf("fork err..\n"); 33 return -1; 34 } 35 if (pid == 0) 36 { 37 close(pipefd[0]); 38 //复制文件描述符pipefd[1],给标准输出,言外之意:execlp的ls命令输出到管道中 39 dup2(pipefd[1], STDOUT_FILENO); 40 close(pipefd[1]); 41 42 execlp("ls", "ls", NULL); 43 //如果替换新的进程印象失败,则会执行下面一句话 44 sprintf(stderr, "execute the cmd ls err..\n"); 45 exit(0); 46 47 48 } 49 else if (pid > 0 ) 50 { 51 int len = 0; 52 char buf[100] = {0}; 53 close(pipefd[1]); 54 //复制文件描述符pipefd[0],给标准输入,言外之意:execlp的wc命令从管道中读 55 dup2(pipefd[0], STDIN_FILENO); 56 close(pipefd[0]); 57 //len = read(pipefd[0], buf, 100); 58 execlp("wc", "wc", "-w", NULL); 59 printf("len:%d, buf:%s \n", len , buf); 60 61 //close(pipefd[0]); 62 } 63 64 wait(NULL); 65 printf("parent ..quit\n"); 66 return 0; 67 68 } 69 70 71 int main(int argc, char *argv[]) 72 { 73 close(0); //关闭标准输入 74 open("makefile", O_RDONLY); //makefile文件变成标准输入 75 close(1);//关闭标准输出 76 open("makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644); //maifle2变成标准输出 77 78 execlp("cat", "cat", NULL); //替换进程印象后,执行cat命令 79 80 //cat命令 从标准输入中按行读,紧接着写到标准输出 81 82 return 0; 83 84 }
改变文件系统描述符实现文件复制:
命名管道FIFO:可以用在不相关的进程之间
匿名管道由pipe函数创建和打开,而命名管道由mkfifo创建,由open打开,相当于创建和打开时分开的。
示例如下:
写管道:
读管道: