1.管道的打开以及关闭操作
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main( void ) { int fd[2]; /* 管道的文件描述符数组 */ char str[256]; if ( (pipe(fd)) < 0 ){ perror("pipe"); exit(1); } write(fd[1], "create the pipe successfully !\n", 1024 ); /*向管道写入端写入数据*/ read(fd[0], str, sizeof(str) ); /*从管道读出端读出数据*/ printf ("%s", str ); printf ( "pipe file descriptors are %d,%d \n", fd[0], fd[1]) ; close (fd[0]); /* 关闭管道的读入文件描述符*/ close (fd[1]); /* 关闭管道的读出文件描述符*/ return 0; }
上边这段程序是管道的打开和关闭的实例,首先定义管道的文件描述符数组fd[2],可以理解为管道的两端,分别是读出端fd[0]和写入端fd[1]端。
用函数write()向管道写入端写入数据, write(fd[1], "create the pipe successfully !\n", 1024 ); ,向写入端fd[1]写入"create the pipe successfully !\n"这句话,数字1024是写入的数据的最大长度。
用函数read()从管道读出端读出数据, read(fd[0], str, sizeof(str) ); ,读出管道端的数据,保存在字符串str中。
2.管道在父子进程之间的应用
根据之前学习的进程操作的知识结合管道的知识我们应该能够写出来如何通过管道使父子进程之间进行通信。
第一步,定义基本的东西,比如文件描述符数组、读出数据存放的数组、进程标识符等。
第二步,创建管道。
第三步,创建一个子进程。
第四步,在父进程中向管道写入数据。
第五步,在子进程中从管道读取数据。
第六步,输出子进程中打印的数据。
这六步是父进程向子进程发送数据的流程,是一个框架,具体的程序写入框架中,这个简单的程序就可以写出来了。但是在写代码的过程中还要注意一个问题,先创建一个管道,再创建一个子进程,父进程中有一个管道,子进程中也有一个管道,父进程中写入的内容怎样才能从子进程的管道中读出来?书上有一个父进程写入数据,子进程读出数据的程序。这个程序在应用中,在父进程中将父进程管道的读数据端关闭,然后写入数据,在子进程中将子进程管道的写数据端关闭,然后再读数据。这样就能在父进程中写数据在子进程中读数据。具体代码如下。
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #define BUFSZ PIPE_BUF /* PIPE_BUF管道默认一次性读写的数据长度*/ int main ( void ) { int fd[2]; char buf[BUFSZ]; pid_t pid; ssize_t len; if ( (pipe(fd)) < 0 ){ /*创建管道*/ perror ( "failed to pipe" ); exit( 1 ); } if ( (pid = fork()) < 0 ){ /* 创建一个子进程 */ perror ( "failed to fork " ); exit( 1 ); } else if ( pid > 0 ){ close ( fd[0] ); /*父进程中关闭管道的读出端*/ write (fd[1], "hello my son!\n", 14 ); /*父进程向管道写入数据*/ exit (0); } else { close ( fd[1] ); /*子进程关闭管道的写入端*/ len = read (fd[0], buf, BUFSZ ); /*子进程从管道中读出数据*/ if ( len < 0 ){ perror ( "process failed when read a pipe " ); exit( 1 ); } else write(STDOUT_FILENO, buf, len); /*输出到标准输出*/ exit(0); } }
上边的代码经过调试之后,发现没有问题,那就证明一开始的思路是正确的,可以通过这样的方式使父子进程之间通过管道进行通信。但是,这是关闭了父进程中管道的读出端才将数据发到了子进程中,如果不关闭父进程中管道的读出能不能传过去?然后修改代码发现不关闭管道的读出端也是可以的。
进一步思考,在父进程中写入的数据能不能在父进程和子进程中同时读出来?经过调试代码,在父进程中添加读管道中的数据并显示的代码如下,发现数据只能从父进程中读出来,不能从子进程中读出来。
else if ( pid > 0 ){ write (fd[1], "hello my son!\n", 14 ); /*父进程向管道写入数据*/ read(fd[0],buf1,BUFSZ); printf(“%s\n%s\n”,buf1,buf1); exit (0); }
这样来看只有两个可能,一种可能就是管道是一次性的,发送接收之后管道就自动清除了。另一种是在父进程的管道写入端写入数据,可以在父进程或子进程的管道读出端读出,但是只能读一次,从父进程中读了之后就不能再在子进程中读,在子进程中读了就不能再在父进程中读。继续修改代码进行调试,先验证第一种可能性。
else if ( pid > 0 ){ write (fd[1], "hello my son!\n", 14); /*父进程向管道写入数据*/ read(fd[0],buf1,BUFSZ); printf(“%s\n%s\n”,buf1,buf1); //第二次写入数据 write (fd[1], "hello my son2!\n", 15 ); /*父进程向管道写入数据*/ read(fd[0],buf1,BUFSZ); printf(“%s\n%s\n”,buf1,buf1); exit (0); }
经过调试发现,管道是一直能用的,这样就推翻了第一种假设。那就剩下第二种可能了,关闭父进程中管道的读出端,代码如下,再调试发现,可以只能从子进程中读出数据。
else if ( pid > 0 ){ close(fd[0]); write (fd[1], "hello my son!\n", 14 ); /*父进程向管道写入数据*/ read(fd[0],buf1,BUFSZ); printf(“%s\n%s\n”,buf1,buf1); exit (0); }
从这一系列的调试过程可以理解管道的工作过程,了解管道在父子进程之间进行通信的过程。
上图中第一幅图是创建管道和子进程之后的管道示意图,但是在通信的时候只能从左边选一根写入的线,在右边选一根读出的线。用的比较多的就是图2中在父进程中通过管道发送数据到子进程中。
3.管道在兄弟进程之间的应用
管道在父子进程之间的应用明白之后,在兄弟之间的应用就很好理解了。
第一步,定义基本的东西,比如文件描述符数组、读出数据存放的数组、进程标识符等。
第二步,创建管道。
第三步,创建第一个子进程。
第四步,在第一个子进程中向管道中写入数据。
第五步,创建第二个子进程
第六步,在第二个子进程中读取管道中的数据。
第七步,输出第二个子进程中打印的数据。
具体代码如下所示:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <fcntl.h> #include <sys/types.h> #define BUFSZ PIPE_BUF void err_quit(char * msg){ perror( msg ); exit(1); } int main ( void ) { int fd[2]; char buf[BUFSZ]; /* 缓冲区 */ pid_t pid; int len; if ( (pipe(fd)) < 0 ) /*创建管道*/ err_quit( "pipe" ); if ( (pid = fork()) < 0 ) /*创建第一个子进程*/ err_quit("fork"); else if ( pid == 0 ){ /*子进程中*/ close ( fd[0] ); /*关闭不使用的文件描述符*/ write(fd[1], "hello brother!\n", 15 ); /*发送消息*/ exit(0); } if ( (pid = fork()) < 0 ) /*创建第二个子进程*/ err_quit("fork"); else if ( pid > 0 ){ /*父进程中*/ close ( fd[0] ); close ( fd[1] ); exit ( 0 ); } else { /*子进程中*/ close ( fd[1] ); /*关闭不使用的文件描述符*/ len = read (fd[0], buf, BUFSZ ); /*读取消息*/ write(STDOUT_FILENO, buf, len); exit(0); } }