管道

一般,进程之间交换信息的方法只能是经由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---管道--全总结(实例入门)的更多相关文章

  1. Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)

    Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)

  2. Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

    Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

  3. Linux进程间通信IPC学习笔记之消息队列(SVR4)

    Linux进程间通信IPC学习笔记之消息队列(SVR4)

  4. Linux 进程间通信(IPC)

    Linux 进程间通信(IPC): Linux系统中除了进程和进程之间通信,我想大家也应该关注用户空间与内核空间是怎样通信的.例如说netlink等等. 除了传统进程间通信外像Socket通信也须要掌 ...

  5. Linux进程间通信IPC学习笔记之有名管道

    基础知识: 有名管道,FIFO先进先出,它是一个单向(半双工)的数据流,不同于管道的是:是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)有一个与路径名关联的名 ...

  6. Linux进程间通信IPC学习笔记之管道

    基础知识: 管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)没有名字: 2)用于共同祖先间的进程通信: 3)读写操作用read和write函数 #incl ...

  7. Linux进程间通信之管道

    1,进程间通信 (IPC ) Inter-Process Communication 比较好理解概念的就是进程间通信就是在不同进程之间传播或交换信息. 2,linux下IPC机制的分类:管道.信号.共 ...

  8. Linux进程间通信(IPC)

    序言 linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的. 而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心) ...

  9. Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)

    整理自网络 Unix IPC包括:管道(pipe).命名管道(FIFO)与信号(Signal) 管道(pipe) 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道 ...

随机推荐

  1. 完美解决CTRL+空格不能切换中/英文输入法的问题

    首先任务栏上的输入法图标上点右键选择设置. 然后选择键设置,双击第一个“在不同的输入语言之间切换”先勾选“切换输入语言”下面选择左手ALT.取消右边“切换键盘布局”前的勾. 然后进入“中文(简体)输入 ...

  2. Html 小插件9 腾讯新闻

    地址:http://minisite.qq.com/others08/ 效果:

  3. API各函数作用简介

    API各函数作用简介 1.控件与消息函数 AdjustWindowRect 给定一种窗口样式,计算获得目标客户区矩形所需的窗口大小 AnyPopup 判断屏幕上是否存在任何弹出式窗口 ArrangeI ...

  4. SQL存储过程动态查询数据区间

    以前经常看到人查询数据库采用left join及case方式,一条一条的枚举查询整个数据的数据区间方法可行,但是数据一但很大,枚举就死悄悄,在网上查看,几乎全是照抄case ,left join枚举无 ...

  5. Git库文件的状态

    git库所在的文件夹(即.git所在的文件夹)中的文件的状态: (1)untracked:未跟踪,此文件在文件夹中,但并没有加入git库,不参与版本控制. 通过”git add”,”git commi ...

  6. cocos2d-x过程动作CCProgressTo示例学习笔记

    // // SpriteProgressToRadial // //------------------------------------------------------------------ ...

  7. 去掉android的屏幕上的title bar

    在配置文件里修改 (关键代码:android:theme="@android:style/Theme.NoTitleBar.Fullscreen",如果想只是去除标题栏就后面不用加 ...

  8. 汉字转拼音的vc++程序源代码

    #include "StdAfx.h" #include "MyChiToLetter.h" // Download by http://www.codefan ...

  9. SQL Server2012新特性概述

    公司最近要升级数据库,SQL Server 2008R2-->2012.再开始升级之前先找了点资料分析一下2012的新特性和功能,提前预热一下. 2012中主要关注一下三个领域: 性能:改进的核 ...

  10. iOS网络请求基础

    这篇是关于网络请求的,结合公司的实际情况编写,如果有不同意见欢迎留言共同讨论. iOS在9.0之后彻底放弃了NSURLConnection,现在已经改用了NSURLSession进行网络请求.一般现在 ...