管道通信.docx
《管道通信.docx》由会员分享,可在线阅读,更多相关《管道通信.docx(10页珍藏版)》请在冰豆网上搜索。
管道通信
Linux的管道通信
进程通信的实用例子之一是Unix系统的管道通信。
Unix系统从SystemV开始提供有名管道和无名管道两种数据通信方式。
无名管道为建立管道的进程及其子孙提供一条以比特流方式传送消息的通信。
该管道在逻辑上被看作管道文件,在物理上则由文件系统的高速缓冲区构成,而很少启动外设。
有名管道可用于两个无关的进程之间的通信。
管道是Linux支持的最初UnixIPC形式之一,在Linux中是一种使用非常频繁的通信机制。
从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:
·限制管道的大小。
实际上,管道是一个固定大小的缓冲区。
在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。
使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
·读取进程也可能工作得比写进程快。
当所有当前进程数据已被读取时,管道变空。
当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
注意:
从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。
写进程在管道的尾端写入数据,读进程在管道的首端读出数据。
数据读出后将从管道中移走,其它读进程都不能再读到这些数据。
管道提供了简单的流控制机制。
进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。
同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
管道的创建
#include<>
intpipe(intfd[2])
函数intpipe(intfd[2])创建一个管道,管道两端可分别用描述字fd[0]以及fd[1]来描述。
需要注意的是,管道的两端是固定了任务的。
即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。
如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。
一般文件的I/O函数都可以用于管道,如close、read、write等等。
使用管道通信时,可关闭某些不需要的读或写描述符,建立起单向的读或写管道,然后用read和write像操作文件一样去操作它:
close(pipe_fd[0]);/*关闭读管道*/
close(pipe_fd[1]);/*关闭写管道*/
发送进程利用文件系统的系统调用write(fd[1],buf,size),把buf中长度为size字节的字符消息送入管道入口(即写入端)fd[1],接收进程则使用系统调用read(fd[0],buf,size)从管道出口(即读出端)fd[0]读出size字节的字符消息放到buf中。
这里,管道按FIFO方式传送消息,且只能单向传送消息。
管道应用实例
例1:
管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。
比如,当在某个shell程序(Bourneshell或Cshell等)键入who│wc-l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。
考虑下面的命令行:
$kill-l显示了当前系统支持的所有信号
$kill-l|grepSIGRTMIN
例2:
使用系统调用pipe()建立一条管道线,两个子进程p1和p2分别向管道各写一句话:
child1issendingamessage!
和child2issendingamessage!
,父进程则从管道中读出来自子进程的信息,并显示在屏幕上。
#include<>
#include<>
main()
{intfd[2];
intpid1,pid2;
charOutPipe[100],InPipe[100];
pipe(fd);
while((pid1=fork())==-1);
if(pid1==0){
printf(“childprocess1%d\n”,getpid());
lockf(fd[1],1,0);/*加锁锁定写入端*/
sprintf(OutPipe,“child1issendingamessage!
”);
write(fd[1],OutPipe,50);/*将buf中的50个字符写入管道*/
sleep(5);/*睡眠5秒,暂时放弃CPU*/
lockf(fd[1],0,0);/*解锁释放写入端*/
exit(0);/*结束进程pid1*/
}else{
while((pid2=fork())==-1);
if(pid2==0){
printf(“childprocess2%d\n”,getpid()”);
lockf(fd[1],1,0);
sprintf(OutPipe,“child2issendingamessage!
”);
write(fd[1],OutPipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}else{
printf(“parentprocess%d\n”,getpid());
wait(0);
read(fd[0],InPipe,50);
printf(“%s\n”,InPipe);
wait(0);
read(fd[0],InPipe,50);
printf(“%s\n”,InPipe);
exit(0);
}
}
}
这里,用到了文件锁函数lockf以实现互斥。
其函数原型为:
#include<>
intlockf(intfiles,intfunction,longsize);
功能:
用作锁定文件的某些段或整个文件
参数:
files是文件描述符;
function是锁定和解锁,1表示锁定,0表示解锁;
size是锁定和解锁的字节数,表示自fd文件的当前位置开始处理size个相连字节,若size值为0,则表示从调用lockf后开始锁定,锁定范围从文件的当前位置到文件尾。
思考:
程序例2中,文件锁是否是必需的
例3:
两个进程,如子进程向父进程发送数据,即使用子进程的fd[1]和父进程的fd[0],同时关闭子进程的fd[0]和父进程的fd[1]。
#include
#include<>
#include<>
#include<>
#include<>
#include<>
int main()
{
char *msg="I am child process!
"; /*子进程发送的数据*/
pid_t pid;
char buf[100];/*用于读取*/
int pi;/*创建管道时的返回值*/
int fd[2];/*创建管道的参数*/
memset(buf,0,sizeof(buf));/*设置buf数组全为0,需*/
pi=pipe(fd); /*要引入#include<>*/
if(pi<0)
{ perror("pipe() error!
");
exit(0);
}
if((pid=fork())==0)/*child process*/
{ close(fd[0]); /*关闭读管道*/
if(write(fd[1],msg,20)!
=-1) /*写入管道*/
printf("childprocess write success!
\n");
close(fd[1]); /*关闭写管道*/
}
else if(pid>0)/*parent process*/
{ close(fd[1]); /*关闭写管道*/
sleep
(2);/*休眠一下等待数据写入*/
if(read(fd[0],buf,100)>0)/*写入管道*/
printf("Message from the pipe is:
%s\n",buf);
close(fd[0]);/*关闭读管道*/
waitpid(pid,NULL,0);/*待pid进程退出,此处pid为子进程*/
exit(0);
}
else
{ perror("fork() error!
");
exit(0);
}
}
管道的局限性
管道的主要局限性正体现在它的特点上:
只支持单向数据流;只能用于具有亲缘关系的进程之间;没有名字;管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。
有名管道
普通管道只能用于一个进程家族之间的通信,如父子,兄弟之间,而命名管道是有“名字”的管道,另外的进程可以看到并使用。
普通管道在内存中,随着进程的结束而消失,命名管道在磁盘上,作为一个特殊的设备文件而存在,进程结束不消失。
值得注意的是,FIFO严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
它们不支持诸如lseek()等文件定位操作。
有名管道可用于两个无关的进程之间的通信。
它的实现函数是:
#include
#include
intmkfifo(constchar*pathname,mode_tmode)
创建一个名为filename的管道,mode模式设置管道的权限,如O_CREAT、O_EXCL、O_NONBLOCK等。
返回:
成功0,出错返回-1错误存储在errno中
有名管道比管道多了一个打开操作:
open。
intopen(constchar*pathname,intflags,mode_tmode);
参数pathname指向欲打开的文件路径字符串;
下列是参数flags所能使用的旗标:
O_RDONLY以只读方式打开文件;O_WRONLY以只写方式打开文件;O_RDWR以可读写方式打开文件。
上述三种旗标互斥,不可同时使用,但可与下列的旗标利用OR(|)运算符组合:
O_CREAT:
若欲打开的文件不存在则自动建立该文件;
O_EXCL:
如果也设置了O_CREAT,此指令会去检查文件是否存在。
若不存在则建立该文件,否则将导致打开文件错误。
此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败;
O_NOCTTY:
打开文件为终端机设备时,不会将该终端机当成进程控制终端机;
O_TRUNC:
若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失;
O_APPEND:
当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面;
O_NONBLOCK:
以不可阻塞的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中;
参数mode一般为0。
返回:
若所有欲核查的权限都通过了检查则返回0,表示成功;只要有一个权限被禁止则返回-1。
注意,使用open()打开FIFO文件时,使用O_NONBLOCK旗标时,必须先有进程以读的方式打开这个管道。
例如执行语句intfp=open(FIFO,O_RDWR|O_NONBLOCK,0);后,语句read(fp,buf,20);将会立即返回读取的数据;如果没有O_NONBLOCK属性,读取将会处于阻塞状态,直到有数据写入该命名管道。
例4:
下面一个实例演示了mkfifo的使用。
请先以超级用户身份登录系统,然后编辑/编译源程序(两个*.c程序),在图形终端上执行读程序,读程序执行后将陷入循环;切换到字符终端1(ctrl+alt+f1),以超级用户身份登录并执行写程序,然后回到图形终端,观察读程序的输出变化。
/**/
#include
#include<>
#include<>
#include<>
#include<>
#include<>
#include
#include<>
#defineFIFO"/home/jkx/myfifo"/*使用宏命名有名管道文件的路径*/
intmain()
{intfd;/*指向命名管道*/
charbuf[100];/*存储数据*/
if(mkfifo(FIFO,O_CREAT|O_EXCL)<0)/*创建管道*/
{perror("Createerror!
\n");
unlink(FIFO);/*清除管道*/
exit(0);
}
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);/*打开管道*/
if(fd<0){
perror("Createerror!
\n");
unlink(FIFO);
exit(0);
}
while
(1){
memset(buf,0,sizeof(buf));/*清空buf数组*/
if(read(fd,buf,100)>0)/*读取管道*/
{printf("Getmessage:
%s\n",buf);
}else{
printf("Notacceptanymessage!
\n");
}
sleep
(1);/*休眠*/
}
}
******************************************
/**/
#include
#include<>
#include<>
#include<>
#include<>
#include<>
#include
#defineFIFO"/home/jkx/myfifo"/*宏定义命名管道路径*/
intmain()
{char*msg="Somemessage!
";/*发送数据*/
intfd;
fd=open(FIFO,O_WRONLY|O_NONBLOCK,0);/*打开*/
if(write(fd,msg,20)!
=-1)/*发送信息*/
printf("MessagehavebeensendtoFIFO\n");
exit(0);
}