进程间通信——管道

来源:互联网 发布:许晴 任盈盈 知乎 编辑:程序博客网 时间:2024/06/08 06:23
为什么要学习进程通信?
因为复杂程序都是多任务的,进程间需要相互协作,但进程间的内存空间又是独立的,所以需要掌握进程的通信让他们相互协作。

管道的概念:

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

1. 其本质是一个伪文件(实为内核缓冲区)

2. 由两个文件描述符引用,一个表示读端,一个表示写端。

3. 规定数据从管道的写端流入管道,从读端流出。


管道分无名管道有名管道
无名管道:只能用于父子进程之间通信
1.创建函数:
头文件:
        #include <unistd.h>
函数原型:
        int pipe(int pipefd[2]);
返回值:成功返回0,失败返回-1
pipefd[0]   ---》读端
pipefd[1]   ---》写端
注意: ①当管道中没有数据时,读阻塞
            ②当管道中的数据写满时,写阻塞
            ③管道文件不知lseek函数
            ④管道中的数据是先进先出的

管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:


1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。

2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

-----------------------------------------------------------------------------------------

程序实例1:父进程写入数据到管道,子进程从管道读出数据并打印

#include <unistd.h>#include <string.h>#include <stdlib.h>#include <stdio.h>#include <sys/wait.h>#include <sys/types.h>int main(){int pipefd[2];char *p = "hello world\n";char buf[100] = {0};if(pipe(pipefd) == -1){printf("创建管道出错!\n");return 0;}pid_t pid  = fork();if(pid > 0){close(pipefd[0]);//父进程关闭读端int ret1 = write(pipefd[1],p,strlen(p));//往管道写入数据if(ret1 < 0)printf("写入管道失败!\n");elseprintf("写入成功!\n");wait(NULL);//等待子进程退出close(pipefd[1]);//写完数据后关闭写端}else if(pid == 0){close(pipefd[1]);//子进程关闭写端int ret2 = read(pipefd[0],buf,sizeof(buf));//读取管道中的数据if(ret2 < 0)printf("读取管道失败!\n");elseprintf("读取成功!\n");//write(STDOUT_FILENO,buf,ret2);//输出到标准输出,即打印显示,与下面等价printf("buf = %s\n",buf);close(pipefd[0]);//读取完数据后关闭读端}else{printf("创建进程失败!\n");return 0;}return 0;}



-------------------------------------------------------------------------------------------------------
实例程序2:
 练习:使用管道实现父子进程间通信,完成:ls -l | wc –l。假定父进程实现ls,子进程实现wc。
#include <stdio.h>  #include <unistd.h>  #include <sys/wait.h>  #include <stdlib.h>int main(void)  {      pid_t pid;      int fd[2];        pipe(fd);      pid = fork();        if (pid == 0) {  //子进程         close(fd[1]);                   //子进程从管道中读数据,关闭写端  dup2(fd[0], STDIN_FILENO);      //让wc从管道中读取数据          execlp("wc", "wc", "-l", NULL); //wc命令默认从标准读入取数据  ,wc -l 命令是统计行数close(fd[0]);     } else if(pid >0){  //父进程        close(fd[0]);   //父进程向管道中写数据,关闭读端  dup2(fd[1], STDOUT_FILENO);     //将ls的结果写入管道中          execlp("ls", "ls","-l", NULL);       //ls输出结果默认对应屏幕close(fd[1]);     }  else{printf("创建进程失败!\n");exit(1);}    return 0;  }  



可见运行程序和在命令行输入命令的结果是一样的。
----------------------------------------------------------------------
有名管道:可以实现同一个系统中任意两个进程通信
有名管道的创建有两种
①命令行创建:mkfifo   /home/gec/myfifo ---》创建管道文件


②管道创建函数
头文件:
        #include  <sys/types.h>
        #include <sys/stat.h>
函数原型:
        int mkfifo(const char *pathname,mode_t mode);
参数一:需要创建的管道路径
参数二:管道的权限,0666
返回值:成功返回0,失败返回-1
注意:①不能再windows文件夹中创建管道文件
         ②open函数当以只读方式或只写方式打开管道时,没有写端或读端打开管道,open会阻塞
        open()函数中O_NONBLOCK---》以不阻塞的方式打开管道,加入打开写端时没有读端被打开,则打开写端失败。
        ③read函数本身是阻塞的,当在管道中没有写端时,则read函数不阻塞。
        ④write函数,当管道文件写满时,write会阻塞,为了保护写入的数据
--------------------------------------------------------
创建一个有名管道,并实现两个进程间(不是父子进程)通信
进程写端:
#include <stdio.h>#include <sys/types.h>#include  <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main(){mkfifo("/home/gec/pi",0666);int fd = open("/home/gec/pi",O_WRONLY);if(fd > 0)printf("open ok\n");printf("in while\n");while(1){char buf[50] = {0};printf("input\n");scanf("%s",buf);write(fd,buf,strlen(buf));}close(fd);}


进程读端:
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main(){mkfifo("/home/gec/pi",0666);int fd = open("/home/gec/pi",O_RDONLY);if(fd >0)printf("open ok\n");printf("in while \n");while(1){char buf[50] = {0};int ret = read(fd,buf,sizeof(buf));if(ret >0)printf("ret  = %s\n",buf);}close(fd);}


进程写端编译:


进程读端编译:


在/home/gec目录下可以看到管道文件pi