Linux进程间通信二之管道(含代码)

来源:互联网 发布:ntfs for mac破解版 编辑:程序博客网 时间:2024/05/21 08:40

Linux进程间通信二之管道

       默认情况下,一个进程会打开三个默认的设备文件:标准输入设备即键盘,标准输出设备即显示器,标准错误输出设备即显示器。默认从标准输入读取信息,将正确的信息写入到标准输出,将错误的信息写入到标准输出。在shell命令应用中“|”可以将两个命令连接起来,将前一个命令的输出作为另一个命令的输入,连接输入输出的中间设备即为一个管道文件。这种管道是临时的,命令执行完后会自动消失,这类管道称为无名管道。
      无名管道是一种特殊的文件,它不属于文件系统,而是对于内核中的一段内存区域,由操作系统管理和维护,应用程序只能通过系统调用访问它。
       无名管道的内核资源会在进程退出后自动释放。不能跟普通文件那样存放大量常规数据。在编程应用方面具有普通文件一样的特点,可以用read/write函数读写,但不能用lseek函数修改读写的位置,管道需要满足先进先出的原则。
     无名管道的操作:
     1.创建无名管道
        int fd[2];
        pipe(fd[2]);
       如果执行成功它会存储两个整型的文件描述符于fd[2],它分别代表管道的两端。系统调用失败会返回-1。无名管道是单向的,即只能从一个进程向另一个进程发送信息。管道中的数据从fd[0]读文件描述符读出,输入到管道中的数据从fd[1]写文件描述符写入。如果需要全双工的需要两个管道。
    2.读写无名管道
      任何的进程读/写无名管道时必须确认还存在一个进程(可以是自己),该进程以写/读的方式访问管道(可以操作相应的文件描述符)。读写管道用read和write,两者以堵塞方式读写管道,可以用fcntl函数修改。
     (1)以堵塞的方式读无名管道,如果当前没有一个进程(包括当前进程)可以访问写端,读操作将立即返回。并按如写操作:
       如果管道现有数据无数据,立即返回0;
       如果管道现有数据大于要读出的数据,立即读出期望大小的数据;
       如果管道现有数据小于要读出的数据,立即读出现有所有的数据;
       
#include<stdio.h>#include<unistd.h>int main(void){   int p[2];   pipe(p);   close(p[1]);   char buf[128];   memset(buf,'\0',128);   int ret=-1;   ret=read(p[0],buf,128);   printf("buf=%s\n",buf);   printf("ret=%d\n",ret);}
情况一验证如上代码;

#include<stdio.h>#include<unistd.h>int main(void){ int p[2]; pipe(p); write(p[1],"hello world",10); close(p[1]); char buf[128]; memset(buf,'\0',128); int ret=-1; ret=read(p[0],buf,3); printf("first ret=%d,buf=%s\n",ret,buf); ret=read(p[0],buf,15); printf("second ret=%d,buf=%s\n",ret,buf);}

情况二三验证如上;
    
(2) 如果以堵塞的方式读无名管道,有某个进程可以访问写端,按如下操作;
   
       如果管道现有数据无数据,读操作堵塞;
       如果管道现有数据大于要读出的数据,读出期望大小的数据;
       如果管道现有数据小于要读出的数据,读出现有所有的数据;
       
#include<stdio.h>#include<unistd.h>int main(void){   int p[2];   pipe(p);   char buf[128];   memset(buf,'\0',128);   read(p[0],buf,128);   printf("buf=%s\n",buf);}
情况一验证如上,二三不再验证;
(3)如果以堵塞的方式写无名管道,但是没有某个进程可以访问读端,写操作将收到SIGPIPE信号,write()返回-1,如果某个进程可以可以访问读端,则写入成功。
#include<stdio.h>#include<unistd.h>#include<signal.h>/*信号处理函数*/void handler(int sig){  if(SIGPIPE==sig)     printf("recv SIGPIPE\n");}int main(){  int p[2];  signal(SIGPIPE,handler);  pipe(p);  close(p[0]);  int ret=0;  ret=write(p[1],"hello world",10);  printf("ret=%d\n",ret);}

验证如上;
补充signal()用法:
       第一个参数指明所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
第二个参数描述了与信号关联的动作,它可以取以下三种值:
1)一个无返回值的函数地址.此函数必须在signal()被调用前申明,handler为这个函数的名字。当接收到一个类型为sig的信号时,就执行handler 所指定的函数。sig是传递给它的唯一参数。执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行handler()函数。当handler()函数执行结束后,控制权返回进程被中断的那一点继续执行。
2)SIG_IGN.这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。
3)SIG_DFL.这个符号表示恢复系统对信号的默认处理。
signal()会依第一个参数指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。返回值:返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。
(4)如果以堵塞的方式写无名管道,但是当前管道已满,写操作就会发生堵塞。若是有好多进程试图写管道,有进程读操作会唤醒写管道,唤醒那一个不一定。写操作写入数据大小建议小于PIPE_BUF大小(默认4096)。
(5)如果以O_NDELAY和O_NONBLOCK设置了管道读端,如果写管道无数据,将立即返回-1.且置errno为EAGAIN错误。
(6)如果以O_NDELAY和O_NONBLOCK设置了管道写端,如果管道无空间,将立即返回-1.且置errno为EAGAIN错误。
有名管道操作:
     无名管道是临时的,完成通信后就会消失。有名管道依赖文件系统,是一个存在的特殊文件,具有磁盘存放路径,文件权限和其他属性;有名管道并没有在磁盘上存放真正的信息,它存储的通信信息在内存中,两个进程结束后会自动丢失,拥有的磁盘路径仅仅是一个接口。通信的两个进程结束后,有名管道的文件路径本身依然存在。
     命令行应用:
     mknod 管道名 p;
     例如:mknod pipe p;
                echo test>pipe&;//写管道
                cat<pipe;//读管道
1.创建有名管道
    mkfifo()函数,第一个参数为建立有名管道文件名,第二个为生成文件的模式。mkfifo()建立有名管道时,该有名管道文件名必须不存在,mkfifo()建立的FIFI文件其他进程可以用读写一般文件的方式存取。执行成功返回0,否则返回-1,失败原因存储在errno中。
2.读写有名管道
   读写操作之前必须先open()函数打开该文件。
   1)如果进程以某种方式打开管道操作,系统将堵塞该进程,直到另一个进程以另一种方式打开该管道后才会继续执行。当然一个进程可以以读写的方式打开管道。
   2)两进程已经完成打开管道操作,堵塞读操作按以下方式进行。
       如果管道现有数据无数据,读操作堵塞;
       如果管道现有数据大于要读出的数据,读出期望大小的数据;
       如果管道现有数据小于要读出的数据,读出现有所有的数据;
   3)两进程已经完成打开管道操作,堵塞写操作按以下方式进行。
     如果管道中没有空间,写操作堵塞;
     如果管道中有空间,但小于欲写入的数据,写满空间后堵塞;
     如果管道中有空间,且大于欲写入的数据,写入数据后返回;
   4)两进程已经完成打开管道操作,中途其中一个进程退出
    未退出一端如果是写操作,将返回SIGPIPE信号;
    未退出一端如果是堵塞读操作,读操作将不再堵塞,直接返回0;
 非亲缘关系进程使用有名管道通信实例:
   向有名管道中写入数据源代码如下:
   
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<limits.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#define FIFO_NAME "/tmp/my_fifo"int main(int argc,char* argv[]){   int pipe_fd;   int res;   char buf[]="hello world!";   if(access(FIFO_NAME,F_OK)==-1)//文件是否存在   {     res=mkfifo(FIFO_NAME,0766);     if(res!=0)     {        fprintf(stderr,"could not create fifo %s\n",FIFO_NAME);       exit(EXIT_FAILURE);     }   }   printf("Process %d opening FIFO O_WRONLY\n",getpid());//打印提示信息   pipe_fd=open(FIFO_NAME,O_WRONLY);   printf("file's descriptor is %d\n",pipe_fd);   if(pipe_fd!=-1)   {     res=write(pipe_fd,buf,sizeof(buf));     if(res==-1)     {       fprintf(stderr,"Write error on pipe\n");       exit(EXIT_FAILURE);     }     printf("write data is %s,%d bytes is write\n",buf,res);//打印写入的数据     close(pipe_fd);   }   else     exit(EXIT_FAILURE);   printf("Process %d finished,\n",getpid());   exit(EXIT_SUCCESS);}        
向有名管道中读取数据源代码如下:
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<limits.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#define FIFO_NAME "/tmp/my_fifo"int main(int argc,char* argv[]){   int pipe_fd;   int res;   char buf[4096];   int bytes_read=0;   memset(buf,'\0',sizeof(buf));   printf("Process %d opening FIFO O_RDONLY\n",getpid());//打印提示信息   pipe_fd=open(FIFO_NAME,O_RDONLY);      printf("file's descriptor is %d\n",pipe_fd);   if(pipe_fd!=-1)   {     bytes_read=read(pipe_fd,buf,sizeof(buf));//读数据输出     printf("the read data is %s\n",buf);     close(pipe_fd);   }   else     exit(EXIT_FAILURE);   printf("Process %d finished,%d bytes read\n",getpid(),bytes_read);   exit(EXIT_SUCCESS);}