Linux进程通信 之 管道.docx
《Linux进程通信 之 管道.docx》由会员分享,可在线阅读,更多相关《Linux进程通信 之 管道.docx(19页珍藏版)》请在冰豆网上搜索。
![Linux进程通信 之 管道.docx](https://file1.bdocx.com/fileroot1/2022-11/24/ebe3609c-d209-46e9-b836-6932d53472ec/ebe3609c-d209-46e9-b836-6932d53472ec1.gif)
Linux进程通信之管道
管道
无名管道
管道包括无名管道和有名管道两种,前者在父子进程中流行,后者由于可以独立成为磁盘文件而存在,因为能够被无血缘关系的进程共享.
无名管道通常直接称为管道,它占用两个文件描述符,不能被非血缘关系的进程共享,一般应用于父子进程.
UNIX中一切皆为文件,管道也是一种文件,称为管道文件.当系统中创建一个管道时,它返回两个文件描述符:
一个文件以只写打开,作为管道的输入端;另一个文件以只读打开,作为管道的输出端.
#include
intpipe(intfildes[2]);
函数pipe在内核中创建一个管道,并分配两个文件描述符标识管道的两端,这两个文件描述符存储与fildes[0]和fildes[1]中.一般约定fildes[0]描述管道和输出端,进程向此文件描述符中读取数据,fildes[1]描述管道的输入端,进程向此文件描述符写入数据.
fildes[0]:
只读文件描述符.
fildes[1]:
只写文件描述符.
Pipe调用成功返回0,否则返回-1.
单向管道流模型
管道的两端(输入端和输出端)被一个进程控制没有太大的意义,如果管道的两端分别控制在不同的进程中,这两个进程之间就能够进行通信.拥有管道输入端的进程,可以向管道发送数据,拥有管道输出端的进程,可以从管道中接受前一个进程发送来的消息.
1)从父进程流向子进程的管道
在父进程创建无名管道并产生子进程后,父子进程均拥有管道两端的访问权.此时关闭父进程的管道输出端,关闭子进程的管道输入端,就形成一个从父进程到子进程的管道流,数据由父进程写入,从子进程读出.
2)从子进程流向父进程的管道
在父进程中创建无名管道并产生子进程后,父子进程均拥有两端的访问权.此时关闭父进程的管道输入端,关闭子进程的管道输出端,就形成了一个从子进程到父进程的管道流.数据由子进程写入,从父进程读出.
#include
#include
#include
#include
intmain()
{
pid_tpid;
intfildes[2];
charbuf[256];
inti,j;
if(pipe(fildes)<0)
{
printf("pipeerror\n");
return-1;
}
if((pid=fork())<0)
{
printf("forkerror!
\n");
return-1;
}
if(0==pid)
{
//子进程
close(fildes[1]);
j=read(fildes[0],buf,sizeof(buf));
buf[j]='\0';
printf("[child]length=[%d]buf=[%s]\n",j,buf);
return0;
}
//父进程
close(fildes[0]);
write(fildes[1],"Hello",strlen("Hello"));
return0;
}
在进程的通信中,我们无法判断每次通信中报文的字节数,即无法对数据流进行自动拆分,从而发生了子进程一次性读取父进程两次通信的报文,为了能正常拆分发送报文,常常采用以下几种方法:
a)固定长度.
b)显式长度.每条报文由长度域和数据域组成.长度域大小固定,储存了数据域长度.分为字符串型和整形两种.数据域是传输的实际报文数据.接收进程先获取长度域的数据,转换成数据域的长度,再读取相应长度的信息即为数据域内容.
c)短连接.每当进程间需要通信时,创建一个通信线路,发生一条报文后立即废弃这调通线路.这种方式在socket通信中很常用.
#include
#include
#include
#include
intmain()
{
pid_tpid;
intfildes[2];
charbuf[256];
inti,j;
if(pipe(fildes)<0)
{
printf("pipeerror\n");
return-1;
}
if((pid=fork())<0)
{
printf("forkerror!
\n");
return-1;
}
if(0==pid)
{
//子进程
close(fildes[1]);
j=read(fildes[0],buf,5);
buf[j]='\0';
printf("[child]length=[%d]buf=[%s]\n",j,buf);
memset(buf,0,sizeof(buf));
j=read(fildes[0],buf,5);
buf[j]='\0';
printf("[child]length=[%d]buf=[%s]\n",j,buf);
memset(buf,0,sizeof(buf));
j=read(fildes[0],buf,5);
buf[j]='\0';
printf("[child]length=[%d]buf=[%s]\n",j,buf);
return0;
}
//父进程
close(fildes[0]);
write(fildes[1],"12345",5);
write(fildes[1],"67890",5);
write(fildes[1],"abcde",5);
return0;
}
双向管道流模型
管道是进程之间的一种单向交流方式,要实现进程间的双向交流,就必须通过两个管道来完成.
1)创建管道,返回两个无名管道文件描述符fildes1和fildes2,fildes1为管道1,fildes2为管道2.
intfildes1[2],fildes2[2];
pipe(fildes1);
pipe(fildes2);
2)创建子进程,子进程继承管道1和管道2.
3)管道1从父进程流程子进程,管道2从子进程流程父进程.
连接标准I/O的管道模型
dup2(fd0,0);//复制fd0到文件描述符0中,更改标准输入为fd0
dup2(fd1,1);//复制fd1到文件描述符1中,更改标准输出为fd1
dup2(fd2,2);//复制fd2到文件描述符2中,更改标准错误输出为fd2
示例一:
#include
#include
#include
usingnamespacestd;
intmain(intargc,char*argv[])
{
intfd;
fd=open("./outlog",O_RDWR|O_CREAT,0755);
dup2(fd,1);//更改标准输出为outlog
cout<<"HolloWorld\n";
return0;
}
示例二:
父进程的输出连接子进程的输入通信实例
#include
#include
#include
#include
usingnamespacestd;
intmain(intargc,char*argv[])
{
intfildes[2];
pid_tpid;
inti,j;
charbuf[256];
if(pipe(fildes)<0||(pid=fork())<0)
{
cout<<"error\n";
return-1;
}
if(0==pid)
{
//子进程
close(fildes[1]);
dup2(fildes[0],0);
close(fildes[0]);
gets(buf);
cout<<"[child]buf="<return0;
}
//父进程
close(fildes[0]);
dup2(fildes[1],1);
close(fildes[1]);
cout<<"HelloWorld"<return0;
}
Popen模型
创建连接标准I/O的管道需要多个步骤,需要使用大量的代码,UNIX提供了一组函数简化了这个复杂的过程.
#include
FILE*popen(constchar*command,char*type);
Intpclose(FIEL*stream);
函数popen类似于函数system,它首先fork一个子进程,然后调用exec执行参数command中给定的shell命令.不同的是,函数popen自动在父进程和exec创建的子进程之间建立了一个管道,这个管道可以连接子进程的标准输入,也可以连接子进程的标准输出,参数type决定了管道的I/O类型
参数type的取值情况
r创建与子进程的标准输出连接的管道(管道数据由子进程流程父进程)
w创建与子进程的标准输入连接的管道(管道数据由父进程流程子进程)
#include
intmain(intargc,char*argv[])
{
FILE*in,*out;
charbuf[256];
if(NULL==(out=popen("grepinit","w")))
{
printf("error\n");
return-1;
}
if(NULL==(in=popen("ps-ef","r")))
{
printf("error\n");
return-1;
}
while(fgets(buf,sizeof(buf),in))
{
fputs(buf,out);
}
pclose(in);
pclose(out);
return0;
}
有名管道
管道如果无名,只能在共同血缘进程中使用,管道如果有名,就可以在整个系统中使用.FIFO管道,有名的管道,它以一种特殊的文件类型储存与文件系统中,以供无血缘的关系进程访问.
Shell命令和C程序都可以创建有名管道.
1)Shell命令创建有名管道
mknodname[b|c]majorminor//创建块设备或字符设备文件
mknodnamep//创建管道文件
mknodnames//创建信号量
mknodnamem//创建共享内存
参数name为创建的文件名称,参数major和minor分别代表主次设备号.
2)命令mkfifo创建管道
mkfifo[–mmode]FILE
其中mode是管道文件创建后的访问权限,FILE是管道文件创建后的名称
eg.创建一个用户本身可读写,其他任何用户只读的管道文件k2.
#mkfifo–m644k2;
3)C函数mkfifo创建管道
UNIX的C语言中,也提供了创建有名管道的函数
#include
#include
Intmkfifo(char*path,mode_tmode);
函数mkfifo创建有名管道,字符串path指定了管道的文件路径和名称,参数mode决定了管道文件的访问权限,它的取值类似于open函数的第三个参数,并且自带了O_CREAT和O_EXCL选项,因此本函数只能创建一个不存在的管道文件,或者返回”文件以存在”错误.如果只是希望打开而不创建文件,请使用函数open或者函数fopen.
有名管道的应用
管道本身就是文件,因此对普通文件的操作也适合于管道文件,可以按照以下步骤应用管道.
1)创建管道文件(应用命令mknod或者mkfifo,或者函数mkfifo)
2)读进程
a)只读打开管道文件(应用函数open或fopen)
b)读管道(应用函数read或者fread)
3)写进程
a)只写打开管道
b)写管道(应用函数write或者fwrite)
4)关闭管道.
低级文件编程库和标准文件编程库都可以操作管道,在打开管道文件前请务必先确认该管道是否存在和是否具备访问权限.
管道在执行读写操作前,两端必须同时打开,否则执行打开管道某端操作的进程将一直阻塞直到某个进程以相反方向打开管道为止.
示例:
写有名管道
#include
#include
#include
#include
usingnamespacestd;
intmain(intargc,char*argv[])
{
FILE*fp=NULL;
charbuf[256];
if(mkfifo("./fifodate",S_IFIFO|0666)<0)
{
cout<<"systemerror"<return-1;
}
while
(1)
{
//打开和关闭管道操作,要在循环里面
if((fp=fopen("./fifodate","w"))==NULL)
{
cout<<"fopenfilefailed"<return-1;
}
cout<<"pleaseinput:
";
memset(buf,0,sizeof(buf));
cin>>buf;
fputs(buf,fp);
fclose(fp);
fp=NULL;
if(0==strcmp(buf,"quit"))
{
break;
}
}
return0;
}
读有名管道
#include
#include
#include
#include
usingnamespacestd;
intmain(intargc,char*argv[])
{
charbuf[256];
FILE*fp=NULL;
while
(1)
{
//打开和关闭管道操作,要在循环里面
if((fp=fopen("./fifodate","r"))==NULL)
{
cout<<"fopenfailed"<return-1;
}
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),fp);
cout<<"readfifomsg:
"<fclose(fp);
fp=NULL;
if(0==strcmp(buf,"quit"))
{
break;
}
}
return0;
}
管道的模型
1)“1-1”模型
本模型应用于两个进程之间的双向通信,设置两个FIFO.进程A的数据从管道1流程流程B,进程B的数据通过管道2流程进程A.
2)“N-1”模型
本进程适用于非交互式服务系统,客户端掌握了公共FIFO的输入端,将消息写入管道,后台服务进程掌握了公共FIFO的输出口,它读取管道中得信息.
3)“N-1-N”模型
本进程适合于交互式服务系统,客户进程掌握了一个总所周知的可以向后台服务进程传递消息的有名管道外,每个客户进程均还拥有一个私有的FIFO.
为了使服务进程正确找到客户进程的私有管道,客户进程务必在其发送的请求消息中增加专用FIFO标识.
管道小结:
管道是进程之间最古老的通信方式,它在UNIX系统中是以一种特殊的文件形式---管道文件.它占用磁盘i节点块和数据块,在目录中记载了文件和i节点对应关系的管道时有名管道,没有记载的是无名管道.
在UNIX中,创建无名管道有pipe,popen等,关闭无名管道的函数有pclose等.创建有名管道的命令有mknod,mkfifo等,创建有名管道的函数有mkfifo.
无名管道应用于父子进程间,实现从父进程到子进程或者从子进程到父进程之间的单向交流.常常实现进程之间的输入输出重定向.
有名管道以管道文件的形式存储与磁盘等外部设备中,可再任意两个进程之间应用,常常实现进程之间的数据交换.
管道还有以下特性
1)虽然无名管道没有路径,不在任何目录文件中记录该目录项,但是它仍然是文件,占用物理上的i节点和数据块.
2)无名管道没有路径,因此无法实现open调用,从而导致无名管道只能在血缘进程中以继承的方式获取访问所需的文件描述符.
3)无名管道文件一旦关闭,就不能再次使用,即使是管道的创建进程也一样.
4)如果管道一次性写入小于PIPE_BUF字节的数据,该写入操作是原子操作,否则数据将先拆分再写入,此时只能保证各个拆分块的原子操作,拆分了之间的写操作不再具备原子性.例如当多个进程同时写超过PIPE_BUF字节的数据到管道时,写入的数据将被拆分成块,这时进程写入到管道的数据中可能插有其他进程的数据.
5)管道最大能够存储PIPE_BUF字节的数据,一旦管道达到最大容量,写管道操作将阻塞,直到管道中数据被读出为止.
6)管道以FIFO(先进先出)方式处理数据.
7)管道数据一旦读出,就从管道中删除,具有不可再现性.
Example:
客户端发送客户端id消息域,服务端接收并放回结果
//服务端接收客户端通过有名管道发送的消息
//消息格式"11+2""1":
表示客户端1,固定一个字节,"1+2":
数据域,固定3个字节
//服务端返回结果"3"到特定的有名管道(./data/data_client1)
#include
#include
#include
#include
#include
#include
usingnamespacestd;
voidOperation(char*_data,char*Child)
{
string_1,_2;
stringstrIn=_data;
intpos=strIn.find("+");
if(pos!
=string:
:
npos)
{
_1=strIn.substr(0,pos);
_2=strIn.substr(pos+1,1);
}
intiRet=atoi(_1.c_str())+atoi(_2.c_str());
charstrRet[3];
sprintf(strRet,"%d",iRet);
intfClient=open(Child,O_RDWR|O_CREAT,0755);
write(fClient,strRet,strlen(strRet));
close(fClient);
cout<<"sendmsg["<return;
}
intmain()
{
intfSrv;
//管道data_serv,服务端读取
if(-1==mkfifo("./data/data_serv",S_IFIFO|0666))
{
cout<<"mkfifodata_serverror!
"<return-1;
}
//管道data_client1,返回给客户端1的管道
if(-1==mkfifo("./data/data_client1",S_IFIFO|0666))
{
cout<<"mkfifodata_client1error!
"<return-1;
}
//管道data_client2,返回给客户端2的管道
if(-1==mkfifo("./data/data_client2",S_IFIFO|0666))
{
cout<<"mkfifodata_client2error!
"<return-1;
}
while
(1)
{
charwhichClient[2];//用于储存客户端编号,客户端发送的编号"1"固定1个字节
chardata[12];//用于存储数据域,客户端发送的数据域"1+2"固定3个字节
charClient[50];
//服务器端读取的内容格式为"11+2"表示"客户端编号数据域":
"1"客户端编号"1+2"数据域
fSrv=open("./data/data_serv",O_RDWR|O_CREAT,0755);
read(fSrv,whichClient,1);//读取客户端进程编号,占一个字节"1"
read(fSrv,data,3);//读取该客户端对应的数据域eg"1+2"
whichClient[1]='\0';
data[3]='\0';
cout<<"recvmsg["<if(strcmp(whichClient,"1