管道

 管道

管道是进程间通信的主要手段之一。一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。根据管道的适用范围将其分为:无名管道和命名管道。管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

2.1 编程分类总结

1,血缘进程间通信
  这里用无名管道,使用fork来复用无名管道的文件操作符,所以只能实现的是有血缘关系的(比如父子,同祖先等)进程间通信。
1.1 父子进程间
创建管道并连接父子进程(pipe+fork)
【说明】先建立pipe,再fork之后会生成子进程,这时父子进程都“共享了”pipe,那么在父进程中输入,子进程用来读出。
1.2 同祖先进程间
   创建管道并连接血缘进程(pipe+fork+exec)
【说明】这里和1.1一样,但是通过exec来替换掉子程序的进程内容,不过需要传递一个参数来表示pipe管道的读端。
创建管道复用标准输入输出连接血缘进程(pipe+fork+dup+exec)
【说明】使用dup主要是用来复用标准输入输出,原理是先关闭标准输入0,那么dup之后会按序找一个可以用的号码(就是0),这样就复用了,这样的目的是不用传递参数了。
2,任意进程间通信
创建有名管道FIFO来实现任意进程间的通信(mkfifo+open+read/write+close)
【说明】这里是建立了一个类似于文件(所以是有名的)的管道,然后用打开open和关闭close来对这个管道进行read/write。这样区别于之前的基于fork的方式来实现了进程间的通信。

2.3 函数popen

popen会调用fork函数建一个子进程

此标准的库函数通过在系统内部调用pipe()来创建一个半双工的管道,然后它创建一个子进程,启动shell,最后在shell上执行command参数中的命令。管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。

[cpp]  view plain  copy
  1. 函数原型:  
  2. FILE *popen(char*command,char*type);  
  3. 参数说明:  
  4. FILE*:返回值,如果成功,返回一个新的文件流。如果无法创建进程或者管道,返回NULL。  

2.4 函数pclose

[cpp]  view plain  copy
  1. 函数原型:  
  2. int pclose(FILE*stream);  
  3. 参数说明:  
  4. int:返回值,返回系统调用wait4()的状态。-1表示错误。  
事实上,popen会fork一个进程,而pclose会wait该子进程并关闭。

【示例代码】:用popen传输大数据

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5.   
  6. int main()  
  7. {  
  8.     FILE *read_fp;  
  9.     char buffer[BUFSIZ+1];  
  10.     int chars_read;  
  11.       
  12.     memset(buffer,'\0',sizeof(buffer));  
  13.     read_fp = popen("ps -ax","r");  
  14.     if(read_fp != NULL)  
  15.     {  
  16.         chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);  
  17.         while(chars_read > 0)  
  18.         {  
  19.             buffer[chars_read] = '\0';  
  20.             printf("Reading:-\n %s\n",buffer);  
  21.             chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);  
  22.         }  
  23.         pclose(read_fp);  
  24.         exit(EXIT_SUCCESS);  
  25.     }  
  26.     exit(EXIT_FAILURE);  
  27. }  

2.5 函数pipe

pipe函数式FIFO方式的底层实现。表示新建了一个管道(定义出了入fd和出fd)。
[cpp]  view plain  copy
  1. 函数原型:  
  2. int pipe(int file_descriptor[2]);  
  3. 参数说明:  
  4. file_descriptor:传出参数,该参数将被函数调用后填写。任何写入file_descriptor[1]的数据可以由file_descriptor[0]中读取。  
  5. int:返回值,-1表示失败。  

【示例程序】单独创建了一个管道(pipe)

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5.   
  6. int main()  
  7. {  
  8.     int data_processed;  
  9.     int file_pipes[2];  
  10.     const char some_data[] = "123";  
  11.     char buffer[BUFSIZ +1];  
  12.       
  13.     memset(buffer,'\0',sizeof(buffer));  
  14.       
  15.     if(pipe(file_pipes) == 0)  
  16.     {  
  17.         data_processed = write(file_pipes[1],some_data,strlen(some_data));  
  18.         printf("Wrote %d bytes\n",data_processed);  
  19.         data_processed = read(file_pipes[0],buffer,BUFSIZ);  
  20.         printf("Read %d bytes: %s\n",data_processed,buffer);  
  21.         exit(EXIT_SUCCESS);  
  22.     }  
  23.     exit(EXIT_FAILURE);  
  24. }  
这个程序使用两个文件描述符file_pipes[]来创建一个管道。然后他使用文件描述符file_pipes[1]向管道中写入数据,并且由file_pipes[0]中读取。注意管道有内部缓冲,从而可以在两个调用write与read之间存储数据。
我们应该清楚尝试使用file_pipes[0]写入数据,或是使用file_pipes[1]读取数据的效果是未定义的,所以这样的行为结果会非常奇怪,并且也许会毫无警告的发生改变。

2.6 函数dup

dup和dup2也是两个非常有用的调用,它们的作用都是用来复制一个文件的描述符。它们经常用来重定向进程的stdin、stdout和stderr。

[cpp]  view plain  copy
  1. 函数原型:  
  2. int dup(int file_descriptor);  
  3. 参数说明:  
  4. file_descriptor:传入的已有文件描述符,该函数创建一个新的文件描述符(作为已有的拷贝)  

2.7 函数dup2

[cpp]  view plain  copy
  1. 函数原型:  
  2. int dup2(int file_descriptor_one , int file_descriptor_two);  
  3. 参数说明:  
  4. file_descriptor_one:传入的已有文件描述符,dup2它所创建的新文件描述符或者与参数file_descriptor_two相同,或者是第一个大于该参数的可用值。  


2.8 无名管道

血缘进程间通信,单向,无名

2.8.1 父子进程间

创建管道并连接父子进程(pipe+fork)

【示例程序】

乍看起来,这个使用管道的例子并无特别之处,它做的工作可以用一个简单的文件来完成。管道的真正优势体现在,当你想在两个进程之间传递数据时,程序用fork创建新进程时,原先打开的文件描述符仍将保持打开的状态。如果在原先的进程中创建一个管道,然后再调用fork创建新进程,我们即可通过管道在两个进程之间传递数据。

[父进程]  ===file_pipes[1]===>  [管道]  ===file_pipes[0]===>  [子进程]

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5.   
  6. int main()  
  7. {  
  8.     int data_processed;  
  9.     int file_pipes[2];  
  10.     const char some_data[] = "123";  
  11.     char buffer[BUFSIZ + 1];  
  12.     pid_t fork_result;  
  13.       
  14.     memset(buffer,'\0',sizeof(buffer));  
  15.       
  16.     if(pipe(file_pipes)==0)  
  17.     {  
  18.         fork_result = fork();  
  19.         if(fork_result == -1)  
  20.         {  
  21.             fprintf(stderr,"Fork failure");  
  22.             exit(EXIT_FAILURE);  
  23.         }  
  24.       
  25.       
  26.         if(fork_result == 0)  
  27.         {  
  28.             data_processed = read(file_pipes[0],buffer,BUFSIZ);  
  29.             printf("Read %d bytes: %s\n",data_processed,buffer);  
  30.             exit(EXIT_SUCCESS);  
  31.         }  
  32.         else  
  33.         {  
  34.             data_processed = write(file_pipes[1],some_data,strlen(some_data));  
  35.             printf("Wrote %d bytes\n",data_processed);  
  36.         }  
  37.     }  
  38.     exit(EXIT_SUCCESS);  
  39. }  

2.8.2 血缘进程间

创建管道并连接血缘进程(pipe+fork+exec)

【示例程序】

[cpp]  view plain  copy
  1. //下面这个程序pipe3.c是管道和exec函数的演示。  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <stdio.h>  
  5. #include <string.h>  
  6.   
  7. int main()  
  8. {  
  9.     int length = 0;  
  10.     int pipes[2];  
  11.     const char data[] = "MONKEY.D.MENG";  
  12.     char buffer[BUFSIZ + 1];  
  13.     pid_t child_pid;  
  14.       
  15.     memset(buffer, 0, sizeof(buffer));  
  16.       
  17.     if (pipe(pipes) == 0)  
  18.     {  
  19.         child_pid = fork();  
  20.         if (child_pid == -1)  
  21.         {  
  22.             fprintf(stderr, "Fork failure!");  
  23.             return EXIT_FAILURE;  
  24.         }  
  25.           
  26.         if (child_pid == 0)  
  27.         {  
  28.             sprintf(buffer, "%d", pipes[0]);  
  29.             execl("pipe4""pipe4", buffer, (char *)0);  
  30.               
  31.             return EXIT_SUCCESS;  
  32.         }  
  33.         else  
  34.         {  
  35.             length = write(pipes[1], data, strlen(data));  
  36.             printf("%d - wrote %d bytes/n", getpid(), length);  
  37.         }  
  38.     }  
  39.     return EXIT_SUCCESS;  
  40. }  
  41.   
  42. //“数据消费者”程序pipe4.c负责读取数据,它的代码要简单的多,如下所示:  
  43. #include <unistd.h>  
  44. #include <stdlib.h>  
  45. #include <stdio.h>  
  46. #include <string.h>  
  47.   
  48. int main(int argc, char *argv[])  
  49. {  
  50.     int length = 0;  
  51.     int file_descriptor;  
  52.     char buffer[BUFSIZ + 1];  
  53.       
  54.     memset(buffer, 0, sizeof(buffer));  
  55.     sscanf(argv[1], "%d", &file_descriptor);  
  56.     length = read(file_descriptor, buffer, BUFSIZ);  
  57.       
  58.     printf("%d -read %d bytes: %s/n", getpid(), length, buffer);  
  59.     return EXIT_SUCCESS;  
  60. }  
  61. 编译程序:  
  62. gcc pipe4.c –o pipe4  
  63. gcc pipe3.c –o pipe3  
  64. 运行程序:  
  65. ./pipe3  
  66. 10733 - wrote 13 bytes  
  67. 10734 -read 13 bytes: MONKEY.D.MENG  
注意这里的两个文件分别是pipe3.c和pipe4.c。在pipe3中先pipe,这样生成了一个管道(有了两个fd名字),然后再fork,在子进程中exec替换为另一个程序pipe4.c。这个时候在pipe4.c中无法知道管道的端口名字,所以必须要用参数传入:execl("pipe4", "pipe4", buffer, (char *)0);其中的buffer就是传入的参数(端口号)。

创建管道复用标准输入输出连接血缘进程(pipe+fork+dup+exec)

原因在于关闭标准输入0后,再dup第一个分配的将是0.

这些进程还只是由一个共同的祖先进程启动的。而不是不相关的进程(因为是用fork)。

【示例程序】

[cpp]  view plain  copy
  1. #include <unistd.h>  
  2. #include <stdlib.h>  
  3. #include <stdio.h>  
  4. #include <string.h>  
  5.   
  6. int main()  
  7. {  
  8.     int data_processed;  
  9.     int file_pipes[2];  
  10.     const char some_data[] = "123";  
  11.     pid_t fork_result;  
  12.     //创建了一个管道,给file_pipes分配了管道的两端的fd值  
  13.     if (pipe(file_pipes) == 0) {  
  14.         fork_result = fork();  
  15.         if (fork_result == (pid_t)-1) {  
  16.             fprintf(stderr, "Fork failure");  
  17.             exit(EXIT_FAILURE);  
  18.         }  
  19.     //子进程  
  20.         if (fork_result == (pid_t)0) {  
  21.             close(0);             //先关闭fd(0)(标准输入)   
  22.             dup(file_pipes[0]);   //复制共用file_pipes[0]的另一个fd,会分配到fd(0)  
  23.             close(file_pipes[0]); //关闭file_pipes[0],这样只有fd(0)作为输入了  
  24.             close(file_pipes[1]); //关闭file_pipes[1],原因是这里子进程只管读出不用输入  
  25.   
  26.             execlp("od""od""-c", (char *)0);  
  27.             exit(EXIT_FAILURE);  
  28.         }  
  29.         //父进程  
  30.         else {  
  31.             close(file_pipes[0]); //关闭file_pipes[0],原因是这里父进程只管输入不管读出  
  32.             data_processed = write(file_pipes[1], some_data,  
  33.                                    strlen(some_data));  
  34.             close(file_pipes[1]); //关闭file_pipes[1],使用完毕  
  35.             printf("%d - wrote %d bytes\n", (int)getpid(), data_processed);  
  36.         }  
  37.     }  
  38.     exit(EXIT_SUCCESS);  
  39. }  

2.9 有名管道

2.9.1 任意进程间通信

打开FIFO文件和打开普通文件的另一点区别在于:对open_flag的O_NONBLOCK选项的用法。使用这个选项不仅改变open调用的处理方式,还会改变对这次open调用返回的文件描述符进行的读写请求的处理方式:
[cpp]  view plain  copy
  1. open(const char *path, ORDONLY);  
  2. 此时,open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则它不会返回。这与前面第一个cat命令的例子类似。  
  3. open(const char *path, ORDONLY | O_NONBLOCK);  
  4. 即使没有其他进程以写方式打开FIFO,这个将成功并立刻返回。  
  5. open(const char *path, O_WRONLY);  
  6. 此时,open调用将阻塞,直到有一个进程以读方式打开同一个FIFO为止。  
  7. open(const char *path, O_WRONLY | O_NONBLOCK);  
  8. 这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO文件,open调用将返回一个错误-1,并且FIFO也不会被打开。如果确实有一个进程以读方式打开FIFO文件,那么我们就可以通过它返回的文件描述符对这个FIFO文件进行写操作。  

创建有名管道FIFO来实现任意进程间的通信(mkfifo+open+read/write+close)
【示例程序】
##client.h定义了头文件和结构信息
[cpp]  view plain  copy
  1. ################client.h  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <stdio.h>  
  5. #include <string.h>  
  6. #include <sys/types.h>  
  7. #include <sys/stat.h>  
  8. #include <fcntl.h>  
  9. #include <limits.h>  
  10.   
  11. #define SERVER_FIFO_NAME "/tmp/serv_fifo"  
  12. #define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo"  
  13.   
  14. #define BUFFER_SIZE 20  
  15.   
  16. struct message  
  17. {  
  18.     pid_t client_pid;  
  19.     char data[BUFFER_SIZE + 1];  
  20. };  
##server.c
[cpp]  view plain  copy
  1. #include "client.h"  
  2.   
  3. int main()  
  4. {  
  5.     int client_fifo_fd;  
  6.     int server_fifo_fd;  
  7.     struct message msg;  
  8.     int read_res = 0;  
  9.     char client_fifo[256];  
  10.     char * tmp_char_ptr;  
  11.   
  12.     //创建了server端的FIFO有名管道,并以只读方式打开,等待client输入  
  13.     mkfifo(SERVER_FIFO_NAME, 0777);  
  14.     server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);  
  15.   
  16.     if (server_fifo_fd == -1)  
  17.     {  
  18.         fprintf(stderr, "Server fifo failure\n");  
  19.         return EXIT_FAILURE;  
  20.     }  
  21.   
  22.     sleep(3);  
  23.   
  24.     do  
  25.     {  
  26.         //从server的FIFO中读取信息  
  27.         read_res = read(server_fifo_fd, &msg, sizeof(struct message));  
  28.         if (read_res > 0)  
  29.         {  
  30.             //将读取的数据转大写处理  
  31.             tmp_char_ptr = msg.data;  
  32.             while(*tmp_char_ptr){  
  33.                 *tmp_char_ptr = toupper(*tmp_char_ptr);  
  34.                 tmp_char_ptr++;  
  35.             }  
  36.             //获取client的FIFO名字,并以写方式打开,写回,关闭  
  37.             sprintf(client_fifo, CLIENT_FIFO_NAME, msg.client_pid);  
  38.             client_fifo_fd = open(client_fifo, O_WRONLY);  
  39.             if (client_fifo_fd != -1)  
  40.             {   
  41.                 write(client_fifo_fd, &msg, sizeof(struct message));  
  42.                 close(client_fifo_fd);  
  43.             }  
  44.         }  
  45.     }while(read_res > 0);  
  46.       
  47.     //关闭server的FIFO  
  48.     close(server_fifo_fd);  
  49.     unlink(SERVER_FIFO_NAME);  
  50.       
  51.     return EXIT_SUCCESS;  
  52. }  
##client.c
[cpp]  view plain  copy
  1. #include "client.h"  
  2.   
  3. int main()  
  4. {  
  5.     int server_fifo_fd;  
  6.     int client_fifo_fd;  
  7.     struct message msg;  
  8.     char client_fifo[256];  
  9.   
  10.     //以只写方式打开server端FIFO,这个名字是公共知晓的  
  11.     server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);  
  12.     if (server_fifo_fd == -1)  
  13.     {  
  14.         fprintf(stderr, "Sorry, no server\n");  
  15.         return EXIT_FAILURE;  
  16.     }  
  17.   
  18.     //定义client端FIFO的名字  
  19.     msg.client_pid = getpid();  
  20.     sprintf(client_fifo, CLIENT_FIFO_NAME, msg.client_pid);  
  21.     //创建client端FIFO  
  22.     if (mkfifo(client_fifo, 0777) == -1)  
  23.     {  
  24.         fprintf(stderr, "Sorry, can not make %s\n", client_fifo);  
  25.         return EXIT_FAILURE;  
  26.     }  
  27.   
  28.     //将数据写入sever端FIFO  
  29.     sprintf(msg.data, "hello world!");  
  30.     printf("%d sent %s\n", msg.client_pid, msg.data);  
  31.     write(server_fifo_fd, &msg, sizeof(msg));  
  32.   
  33.     //以只读方式打开client端FIFO,并关闭  
  34.     client_fifo_fd = open(client_fifo, O_RDONLY);  
  35.     if (client_fifo_fd != -1)  
  36.     {  
  37.         if (read(client_fifo_fd, &msg, sizeof(msg)) > 0)  
  38.         {  
  39.             printf("received : %s\n", msg.data);  
  40.         }  
  41.         close(client_fifo_fd);  
  42.     }  
  43.   
  44.     //关闭server端FIFO  
  45.     close(server_fifo_fd);  
  46.     unlink(client_fifo);  
  47.   
  48.     return EXIT_SUCCESS;  
  49. }  

2.10 函数mkfifo

[cpp]  view plain  copy
  1. 函数原型:  
  2. int mkfifo( const char *filename , mode_t mode);    
【代码示例】
[cpp]  view plain  copy
  1. #include <unistd.h>  
  2. #include <stdlib.h>  
  3. #include <stdio.h>  
  4. #include <sys/types.h>  
  5. #include <sys/stat.h>  
  6.   
  7.   
  8. int main()  
  9. {  
  10.     int res = mkfifo("/tmp/my_fifo", 0777);  
  11.     if (res == 0)  
  12.         printf("FIFO created\n");  
  13.     exit(EXIT_SUCCESS);  
  14. }  

2.11 函数mknod

xxx

2.12 有名管道vs无名管道vs文件

【无名管道】(函数pipe,公用内存)不属于任何文件系统(不可见),只存在于内存中。适合通过fork来复用(利用fork后父子进程继承了打开的描述符),也正因为用fork,所以才决定了它只能在有亲缘关系的进程间通信。
【有名管道】(函数mkfifo,有inode,但数据块只在内存中)是有名有形的,为了使用这种管道,LINUX中设立了一个专门的特殊文件系统--管道文件,它存在于文件系统中,任何进程可以在任何时候通过有名管道的路径和文件名来访问管道。但是在磁盘上的只是一个节点,而文件的数据则只存在于内存缓冲页面中,与普通管道一样。可以用临时文件来替代么??不能,我觉得有名管道FIFO因为有了打开方式的限制(只读,只写,阻塞,非阻塞的各种组合),使得在阻塞方式下保证了管道两端必须一端是读一端是写,实现了同步!

【文件】(有inode,有硬盘数据块

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值