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) 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道 ...
随机推荐
- 转: when.js原理和核心实现
这篇文章可以看作是屈屈同学关于when.js的文章<异步编程:When.js快速上手>的续篇. 屈屈的文章中详细介绍了when.js,在这里关于when.js的使用我就不多复述了,大家可以 ...
- Windows Azure 虚拟网络中虚拟机的网络隔离选项
最近我们发布了一份<Windows网络安全白皮书>(单击此处下载),文中深入说明了客户可以如何利用该平台的本地功能,为他们的信息资产提供最好的保护. 由首席顾问Walter Myer ...
- 使用COCOS2D-X JSB开发,在js页面中设置iOS键盘模式
XYSDK.h void setKeyboardType(int type); XYSDK.cpp voidXYSDK::setKeyboardType(int type) { #if (CC_TAR ...
- android开发_SimpleAdapter适配器
android开发_SimpleAdapter适配器 新建项目: 项目结构: drawable-hdpi文件夹中的图片是自己加入的.主要是在菜单选项中显示的图片: 运行效果: 代码部分: main ...
- 线性规划问题的matlab求解
函数:[x, fval] = linprog(f, A, b, Aeq, Beq, LB, UB) 返回的x:是一个向量——在取得目标函数最小时各个xi的取值: 返回的fval:目标函数的最小值: 参 ...
- android 围绕中心旋转动画
本文主要介绍Android中如何使用rotate实现图片不停旋转的效果.Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移.缩放.旋转)产生动画效果 ...
- python邮件发送脚本
转自:http://phinecos.cnblogs.com/ #!/usr/bin/python #coding=utf-8 #@author:dengyike #@date:2010-09-28 ...
- 如何在Mac系统安装eclipse并运行java程序?
链接地址:http://jingyan.baidu.com/article/7f41ecece8ef5b593c095c71.html eclipse现在也有 Mac版了,我们快来试一试吧!现在我将带 ...
- poj 1753 Flip Game 高斯消元
题目链接 4*4的格子, 初始为0或1, 每次翻转一个会使它四周的也翻转, 求翻转成全0或全1最少的步数. #include <iostream> #include <vector& ...
- 一个Sqrt函数引发的血案(转)
作者: 码农1946 来源: 博客园 发布时间: 2013-10-09 11:37 阅读: 4556 次 推荐: 41 原文链接 [收藏] 好吧,我承认我标题党了,不过既然你来了, ...