UNIX/Linux进程间通信IPC---管道--全总结(实例入门)
管道
一般,进程之间交换信息的方法只能是经由fork或exec传送打开文件,或者通过文件系统。而进程间相互通信还有其他技术——IPC(InterProcessCommunication)
(因为不同的进程有不同的进程空间,我们无法自己设定一种数据结构 使不同的进程都可以访问,故需要借助于操作系统,它可以给我们提供这样的机制。IPC)
管道是UNIX系统IPC的最古老的形式,并且所有UNIX系统都提供此种通信机制。
但是其有局限性:
①它们是半双工的(即数据只能在一个方向上流动)
②它们只能在具有公共祖先的进程之间使用。(通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道)
尽管有这两种局限性,半双工管道仍是最常用的IPC形式。
创建管道
#include <unistd.h>
int pipe (int fd[2]) ;
该函数返回两个文件描述符:fd[0]和fd[1]。前者打开来读,后者打开来写。提供一个单路(单向)数据流。
单进程的管道几乎没有任何用处,通常调用pipe的进程接着调用fork,这样就创建了从父进程到子进程的IPC通道。
管道的典型用途:
以下述方式为两个不同进程(一个是父进程,一个是子进程)提供进程间通信手段。
首先,由一个进程(将成为父进程)创建一个管道后 调用fork派生一个自身的副本。
接着,父进程关闭这个通道的读出端,子进程关闭同一通道的写入端。这就在父子进程间提供了一个单向数据流。
(管道时在内核中的数据结构,故fork不会复制管道。但管道的文件描述符在进程空间中,故fork之后,子进程获得了管道的文件描述符副本)
【注意】
当管道的一端被关闭后:
①当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,以指示达到了文件结束处。
②当写一个读端已被关闭的管道时,则产生信号SIGPIPE,write返回-1,errno设置为EPIPE。
用管道实现:客户--服务器模型
管道是在内核中的,因此从客户到服务器以及从服务器到客户的所有数据都穿越了用户--内核接口两次:一次是在写入管道时,一次是在从管道读出时。
- //客户--服务器模型:
- //客户从标准输入获取请求文件的路径,用管道传给服务器
- //服务器从管道获得文件路径后,读取文件内容,把文件内容写入到另一管道
- //客户从管道获取文件内容后 把其显示在标准输出
- //
- //此启动父子进程不需要同步。
- //若server先启动,它会阻塞在read上(等待client向管道写入消息)
- //若client先启动,它会阻塞在fgets获取用户请求上
- //故谁先启动都行。
- //
- //
- #include <sys/types.h>
- #include <unistd.h> //包含pipe()
- #include <sys/wait.h>
- #include <sys/stat.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <string.h>
- #include <limits.h>
- #include <fcntl.h>
- #define MAXLINE 1024
- void client(int, int) ;
- void server(int, int) ;
- int
- main(int argc, char** argv)
- {
- int pipe1[2], pipe2[2] ;
- pid_t childpid ;
- //创建两个管道
- pipe(pipe1) ;
- pipe(pipe2) ;
- if ((childpid = fork()) == 0) //子进程
- {
- //关闭管道1的写描述符和管道2的读描述符
- close(pipe1[1]) ;
- close(pipe2[0]) ;
- //启动服务器函数
- server(pipe1[0], pipe2[1]) ;
- exit(0) ;
- }
- //父进程
- //关闭管道1的读描述符和管道2的写描述符
- close(pipe1[0]);
- close(pipe2[1]);
- //启动客户函数
- client(pipe2[0], pipe1[1]) ;
- //等待子进程结束
- waitpid(childpid, NULL, 0) ;
- exit(0) ;
- }
- void
- server(int readfd, int writefd)
- {
- int fd ;
- ssize_t n ;
- char buff[MAXLINE+1] ;
- //从管道读取来自客户端的请求消息
- if ((n = read(readfd, buff, MAXLINE)) == 0)
- perror("EOF while readingpathname") ;
- buff[n] = '\0' ;
- //以只读方式打开用户请求的文件
- if ((fd = open(buff, O_RDONLY)) < 0)
- {
- //打开失败,向用户返回出错信息
- snprintf(buff + n, sizeof(buff)-n,":can't open, %s\n",
- strerror(errno)) ;
- n = strlen(buff) ;
- write(writefd, buff, n) ;
- }
- else //打开成功
- { //读取文件内容 写入到管道中
- while ((n = read(fd, buff, MAXLINE))> 0)
- write(writefd, buff, n) ;
- close(fd) ;
- }
- }
- //readfd、writefd是用于与管道对接的
- //client从管道的readfd读消息,向管道的writefd写消息
- void
- client(int readfd, int writefd)
- {
- size_t len ;
- ssize_t n ;
- char buff[MAXLINE] ;
- //从标准输入获取用户的请求(文件路径)
- fgets(buff, MAXLINE, stdin) ;
- len = strlen(buff) ;
- if (buff[len-1] == '\n')
- len-- ;
- //把用户请求的文件路径写入管道
- write(writefd, buff, len) ;
- //从管道中读取服务器端的响应消息,并输出到标准输出
- while ((n = read(readfd, buff, MAXLINE))> 0)
- {
- write(STDOUT_FILENO, buff, n) ;
- }
- }
用管道实现:父子进程间同步
- //管道可用来实现父子进程间的同步
- //
- //
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- static int pfd1[2] ; //父进程在管道1上阻塞等待 来自子进程的消息
- static int pfd2[2] ; //子进程在管道2上阻塞等待 来自父进程的消息
- //初始化同步机制
- void
- TELL_WAIT(void)
- {
- if (pipe(pfd1) < 0 || pipe(pfd2) < 0)
- perror("pipe error!\n") ;
- }
- //通知父进程(即向管道1写入消息,会唤醒在其上等待的父进程)
- void
- TELL_PARENT(pid_t pid)
- {
- if (write(pfd1[1], "c", 1) != 1)
- perror("write error!\n") ;
- }
- //等待父进程(在管道2上读阻塞)
- void
- WAIT_PARENT(void)
- {
- char c ;
- if (read(pfd2[0], &c, 1) != 1)
- perror("read error\n") ;
- //检查是否是来自父进程的数据(防止伪造)
- if (c != 'p')
- perror("WAIT_PARENT:incorrectdata\n") ;
- }
- //通知子进程(即向管道2写入消息,会唤醒在其上等待的子进程)
- void
- TELL_CHILD(pid_t pid)
- {
- if (write(pfd2[1], "p", 1) != 1)
- perror("write error!\n") ;
- }
- //等待子进程(在管道1上读阻塞)
- void
- WAIT_CHILD(void)
- {
- char c ;
- if (read(pfd1[0], &c, 1) != 1)
- perror("read error\n") ;
- //检查是否是来自子进程的数据(防止伪造)
- if (c != 'c')
- perror("WAIT_PARENT:incorrectdata\n") ;
- }
- //------------------测试程序--------------------
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- int
- main(void)
- {
- int i = 0 ;
- int cnt = 0 ;
- pid_t pid ;
- //为同步机制初始化
- TELL_WAIT() ;
- if ((pid = fork()) < 0)
- perror("fork error") ;
- else if (pid > 0) //父进程
- {
- for (i = 0; i < 3; ++i)
- {
- printf("From parent :%d\n", i) ;
- }
- TELL_CHILD(pid) ;
- WAIT_CHILD(pid) ;
- for (; i < 6; ++i)
- {
- printf("From parent :%d\n", i) ;
- }
- TELL_CHILD(pid) ;
- return 0 ;
- }
- else //子进程
- {
- WAIT_PARENT(getppid()) ;
- for (i = 0; i < 3; ++i)
- {
- printf("From child : %d\n",i) ;
- }
- TELL_PARENT(getppid()) ;
- WAIT_PARENT(getppid()) ;
- for (; i < 6; ++i)
- {
- printf("From child :%d\n", i) ;
- }
- return 0 ;
- }
- }
- //只要有:阻塞-等待的机制 都可以考虑实现进程间同步。(信号、管道等)
popen和pclose函数
这两个函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。(把这一系列操作打包了)
#include <stdio.h>
FILE* popen(constchar* cmdstring, const char* type) ;
int pclose(FILE* fp) ;
popen在调用进程和所指定的命令之间创建一个管道。若参数type是”r”,则返回的文件指针连接到cmdstring的标准输出。(与文件打开fopen类比)
(这两个函数使我们可以很方便地利用shell命令来辅助我们实现程序的功能,减少了需要编写的代码量。)
(popen的作用就是重定向shell命令的输入/输出)
- //客户--服务器模型:
- //用cat命令实现
- //
- #include <sys/types.h>
- #include <unistd.h> //包含pipe()
- #include <sys/wait.h>
- #include <sys/stat.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <string.h>
- #define MAXLINE 1024
- int
- main(int argc, char** argv)
- {
- size_t n ;
- char buff[MAXLINE] ;
- char command[MAXLINE] ;
- FILE* fp ;
- //从标准输入获取用户请求文件路径
- fgets(buff, MAXLINE, stdin) ;
- n = strlen(buff) ;
- if (buff[n-1] == '\n')
- buff[n-1] = '\0' ;
- //把文件路径与cat 组成shell命令
- snprintf(command, sizeof(command),"cat %s", buff) ;
- fp = popen(command, "r") ;
- //把执行shell命令的结果 拷贝到标准输出
- while (fgets(buff, MAXLINE, fp) != NULL)
- fputs(buff, stdout) ;
- exit(0) ;
- }
协同进程
当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则该过滤程序就成为协同进程。(即其标准输入/输出都重定向到了另一个进程,专门为另外一个进程服务的)
(协同进程主要强调,我们把原先用于输入/输出的程序 改造成了为我所用。它强调不修改原程序的代码,使其为我所用。而先前的客户-服务器程序两个进程都是我们撰写的 两进程中都要对约定的管道进行操作,而协同进程对我们来说是黑盒,其代码不知道有管道。)
popen只提供连接到另一个进程的标准输入或标准输出的一个单向管道,而对于协同进程,则它有连接到另一个进程的两个单向管道——一个接到其标准输入,另一个则来自其标准输出。我们先要将数据写到其标准输入,经其处理后,再从其标准输出读取数据。
【注意】
协同进程对于输入/输出的处理函数。若是低级I/O函数(write、read),没问题。若是标准I/O函数,则需要做一些处理。
因为,标准输入是个管道,所以系统默认标准I/O是全缓冲的(对于终端,默认行缓冲),对于标准输出也是如此。
这样,当协同进程从其标准输入读阻塞时,主进程从管道也读阻塞。(因为协同进程标准IO函数全缓冲,未把数据写入管道)会产生死锁。
故,要更改协同进程的缓冲类型。
setvbuf(stdin, NULL, _IOLBF, 0) ; //设置行缓冲
setvbuf(stdout, NULL, _IOLBF, 0) ;
若我们无法更改协同进程的源代码,则必须使被调用的协同进程认为它的标准输入和输出都被连接到一个终端。可用伪终端实现这一点。(略)
【示例】
- //协同进程:
- //从标准输入读入两个数,相加,输出到标准输出。
- //
- //
- #include <pthread.h>
- #include <stdio.h>
- #include <errno.h>
- #include <stdlib.h>
- #define MAXLINE 1024
- int
- main(void)
- {
- int n1, n2 ;
- char line[MAXLINE] ;
- setvbuf(stdin, NULL, _IOLBF, 0) ; //设置行缓冲
- setvbuf(stdout, NULL, _IOLBF, 0) ;
- //用标准IO从键盘获取用户输入
- while (fgets(line, MAXLINE, stdin) != NULL)
- {
- if (sscanf(line, "%d%d",&n1, &n2) == 2)
- if (printf("%d\n", n1+n2)== EOF)
- perror("printferror\n") ;
- }
- exit(0) ;
- }
- //协同进程的主控进程
- //
- //
- #include <unistd.h>
- #include <signal.h>
- #include <stdio.h>
- #include <errno.h>
- #include <string.h>
- #include <stdlib.h>
- #define MAXLINE 1024
- void sig_pipe_handler(int signo);
- int
- main(void)
- {
- size_t n ;
- pid_t pid ;
- int fd1[2], fd2[2] ; //用于连接协同进程的两个管道
- char line[MAXLINE] ;
- //涉及管道的,设置一个对SIGPIPE信号的处理程序
- if (signal(SIGPIPE, sig_pipe_handler) ==SIG_ERR)
- perror("signal error\n") ;
- //创建两个管道用于和协同进程通信
- if (pipe(fd1) < 0 || pipe(fd2) < 0)
- perror("pipe error\n") ;
- if ((pid = fork()) < 0)
- perror("fork error\n") ;
- else if (pid > 0) //父进程(向管道发送消息,从管道获取协同进程对消息的处理结果)
- {
- //关闭不用的管道文件描述符
- close(fd1[0]) ;
- close(fd2[1]) ;
- while ((fgets(line, MAXLINE, stdin)) !=NULL)
- {
- n = strlen(line) ;
- //把用户的输入 写到管道 传给协同进程
- if (write(fd1[1], line, n) != n)
- perror("write error frompipe\n") ;
- //从管道读取协同进程的反馈
- if ((n = read(fd2[0], line,MAXLINE)) < 0)
- perror("read error frompipe\n") ;
- if (n == 0)
- {
- perror("child closepipe\n") ;
- break ;
- }
- //把协同进程的处理结果 打印到屏幕
- line[n] = 0 ;
- if (fputs(line, stdout) == EOF)
- perror("fputserror\n") ;
- }
- exit(0) ;
- }
- else //子进程(把两管道分别连接到协同进程的 输入/输出端)
- {
- close(fd1[1]) ;
- close(fd2[0]) ;
- //把标准输入描述符 重新设置为 管道的fd1[0]
- if (fd1[0] != STDIN_FILENO)
- {
- if (dup2(fd1[0], STDIN_FILENO) !=STDIN_FILENO) //dup2用于复制描述符
- perror("dup2error\n") ;
- close(fd1[0]) ;
- }
- //把标准输出描述符 重新设置为 管道的fd2[1]
- if (fd2[1] != STDOUT_FILENO)
- {
- if (dup2(fd2[1], STDOUT_FILENO) !=STDOUT_FILENO) //dup2用于复制描述符
- perror("dup2error\n") ;
- close(fd2[1]) ;
- }
- //启动协同进程
- if (execl("./add","add", (char*)0) < 0)
- perror("execl error\n") ;
- }
- exit(0) ;
- }
- void
- sig_pipe_handler(int signo)
- {
- printf("SIGPIPE caught\n");
- exit(1) ;
- }
UNIX/Linux进程间通信IPC---管道--全总结(实例入门)的更多相关文章
- Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)
Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)
- Linux进程间通信IPC学习笔记之同步二(Posix 信号量)
Linux进程间通信IPC学习笔记之同步二(Posix 信号量)
- Linux进程间通信IPC学习笔记之消息队列(SVR4)
Linux进程间通信IPC学习笔记之消息队列(SVR4)
- Linux 进程间通信(IPC)
Linux 进程间通信(IPC): Linux系统中除了进程和进程之间通信,我想大家也应该关注用户空间与内核空间是怎样通信的.例如说netlink等等. 除了传统进程间通信外像Socket通信也须要掌 ...
- Linux进程间通信IPC学习笔记之有名管道
基础知识: 有名管道,FIFO先进先出,它是一个单向(半双工)的数据流,不同于管道的是:是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)有一个与路径名关联的名 ...
- Linux进程间通信IPC学习笔记之管道
基础知识: 管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)没有名字: 2)用于共同祖先间的进程通信: 3)读写操作用read和write函数 #incl ...
- Linux进程间通信之管道
1,进程间通信 (IPC ) Inter-Process Communication 比较好理解概念的就是进程间通信就是在不同进程之间传播或交换信息. 2,linux下IPC机制的分类:管道.信号.共 ...
- Linux进程间通信(IPC)
序言 linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的. 而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心) ...
- Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)
整理自网络 Unix IPC包括:管道(pipe).命名管道(FIFO)与信号(Signal) 管道(pipe) 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道 ...
随机推荐
- 自己动手实现getElementsByClassName
看了一句话,我们都是搬运工,github的搬运工,下面这代码搬运来自各个地方,最后成型. var classCache = {}; function getElementsByClassName(cl ...
- Unix Shells: Bash, Fish, Ksh, Tcsh, Zsh
Hyperpolyglot Unix Shells: Bash, Fish, Ksh, Tcsh, Zsh grammar | quoting and escaping | charactersvar ...
- CSS 常用自定义样式
目录: 1. 文本单行显示,并对超出部分截断以省略号代替: 2.列布局或栅格布局:比如:左侧固定宽度,右侧占满剩下的宽度: 章节: 1. 文本单行显示,并对超出部分截断以省略号代替:参见以下代码: d ...
- python 在 for i in range() 块中改变 i 的值的效果
先上一段代码: for i in range(3): i = 2 print(i) 实际结果是: 2 2 2 可以发现实际效果就是 在每次执行 for 语句块的内容后 i 会被重新赋值
- VC++深入详解-第五章学习心得
这一章节主要讲解了文本相关的一些编程 插入符的使用 CreateSolidCaret(100,200);//插入符的宽度和高度 ShowCaret(); 插入符的一般使用方法 int CTestVie ...
- AppStore被拒原因及总结
4.5 - Apps using background location services must provide a reason that clarifies the purpose of th ...
- HDU5280 Senior's Array(简单DP)
题目链接:pid=5280">传送门 题意: 给定一个长度为n的序列,和一个改动的值p,必须从原序列中选一个位置改动成p, 求改动后的区间和的最大值. 分析: 枚举位置+最大区间和. ...
- getline函数
在我的印象中,getline函数常常出如今自己的视野里,模糊地记得它常常常使用来读取字符串 .可是又对它的參数不是非常了解,今天又用到了getline函数,如今来细细地总结一下: 首先要明确设计get ...
- golang(5):编写WebSocket服务,client和html5调用
本文的原文连接是: http://blog.csdn.net/freewebsys/article/details/46882777 转载请必须注明出处! 1.关于websocket HTML5定义了 ...
- Android 开发UI牛博[转]
Android 新兴的UI模式——侧边导航栏 侧边导航栏也就是大家熟知的SliddingMenu,英文也叫Fly-In App Menu.Side Navigation等.当然谷歌现在已经推出类似这个 ...