Linux 通信——无名管道

Linux 通信——管道详解

Linux 进程间通信(IPC)由以下几部分发展而来:
      早期UNIX进程间通信、基于System V进程间通信、基于Socket进程间通信和POSIX进程间通信。
      UNIX进程间通信方式包括:管道、FIFO、信号。
      System V进程间通信方式包括:System V消息队列、SystemV信号灯、System V共享内存、
      POSIX进程间通信包括:posix消息队列、posix信号灯、posix共享内存。
现在linux使用的进程间通信方式:
    (1)管道(pipe)和有名管道(FIFO)    半双工 相当于两个进程通过一个文件交流
    (2)信号(signal)                      通过发送信号控制/通知另一个进程
    (3)消息队列                               一个进程把消息放进链表,其他进程去读取
    (4)共享内存                               开辟一块内存大家在上面交流
    (5)信号量                                  排队去访问共享的一个资源
    (6)套接字(socket)

Linux进程通信已基本学完,现在我着重复习管道通信的相关知识点。

管道

         管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。管道包括无名管道和命名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。

无名管道

    无名管道由pipe( )函数创建:

    #include <unistd.h>

    intpipe(int filedis[2]);

    pipe()函数的参数是一个由两个整数类型的文件描述符组成的数组指针。该函数在数组中填上两个新的文件描述符后返回0,如果失败则返回-1并设置errno以表明失败原因——

         EMFILE进程已用完文件描述词最大量。

         ENFILE 系统已无文件描述词可用。

         EFAULT 参数 filedes 数组地址不合法。

        当一个管道被创建时,它会创建两个文件描述符:filedis[0]用于读管道,filedis[1]用于写管道。数据基于先进先出的原则(FIFO)进行处理。  特别注意:这里使用的是文件描述符,而不是文件流,我们必须用底层的read和write调用来访问数据,因为管道不是正规的文件,不能使用fread和fwrite。 

    

write:ssize_twrite(int fd, const void *buf, size_t count);

          数据按到达顺序依次写入管道。通常(清除O_NONBLOCK),如果管道满了,write将阻塞,直到read移除了足够的旧数据;没有局部写。如果设置了O_NONBLOCK,而且将被写入的数量是PIPE_BUF或者更少,则write要么立即写入数据,要么返回-1且把errno设置为EAGAIN;没有局部写。但如果数量超过PIPE_BUF,局部写是有可能的。

                                                            写管道

 O_NONBLOCK

写的总数

无立即可写的

部分立即可写的

所有立即可写的

清除

<=PIPE_BUF

阻塞;完全写;原子的;

阻塞;完全写;原子的;

不阻塞;完全写;原子的

清除

>PIPE_BUF

阻塞;完全写;非原子的

阻塞;完全写;非原子的;

可能阻塞;完全写;非原子

设置

<=PIPE_BUF

EAGAIN

EAGAIN

不阻塞;完全写;原子的

设置

>PIPE_BUF

EAGAIN

不阻塞;部分或EAGAIN;原子的

不阻塞;完全、部分或EAGAIN;非原子的

   

 read:ssize_tread(int fd, void *buf, size_t count);

        和写入时一样,按到达顺序依次读取管道数据。通常(清除O_NONBLOCK),如果管道是空的,read将阻塞,直到至少有一字节的数据可用,除非关闭所有的写入文件描述符,这种情况下,read返回0(通常是文件结束指示)。但read的第三个参数的字数节不必满足——只要和那个时刻读取的字节相同,并且返回一个合适的计数就行了。当然,永远也不会超越该字节计数;下一个读操作可以读没有被读的字节。如果设置了O_NONBLOCK,那么空管道上的读操作将返回-1,并且设置errno为EAFAIN

                             读取管道                        

O_NONBLOCK

无立即可读的

部分或所有立即可读的

清除

除非没有写操作,否则阻塞(返回0)

不阻塞;可能部分读

设置

除非没有写操作,否则为EAGAIN(返回0)

不阻塞;可能部分读

 

①用管道进行单向通信:


常用方法:

    1、创建管道。

    2、派生创建读子进程。

    3、在子进程中,关闭管道的写结尾,并做好所需准备。

    4、在子进程中,执行该子进程的程序。

    5、在父进程中,关闭管道的读结尾。

    6、如果第二个子进程需要写管道,那就创建它,做好必要准备,并执行其程序。如果父进程准备写,那么直接写就可以了。

 示例:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6         char buff[BUFSIZ];
  7         int fds[2];     
  8         pid_t pid;
  9         int len;
 10         int err;
 11         if(err=pipe(fds)){         //创建管道
 12                perror("pipe");
 13                 return 1;
 14         }
 15         pid=fork();              //调用fork创建一个读子进程
 16         if(pid==0){
 17                 close(fds[1]); //在子进程中,关闭管道写结尾
 18                 while(1){
 19                        if(len=read(fds[0],buff,BUFSIZ)>0)
 20                        printf("%s\n",buff);
 21                        }
 22                 close(fds[0]);
 23         }
 24         if(pid>0){
 25                 close(fds[0]); //在父进程中,关闭管道读结尾
 26                 while(1){
 27                        len=write(fds[1],"hello",6);
 28                        printf("write return len is %d\n",len);
 29                        sleep(1);
 30                 }
 31                 close(fds[1]);
 32          }
 33         return 0;
 34 }

运行结果:


 

②用管道进行双向通行:


方法与单向通信类似:

    1、分别创建管道1和2。

    2、派生创建一个子进程。

    3、在子进程中,关闭管道1的写入端和管道2的读出端,并做好所需准备。

    4、在子进程中,执行该子进程的程序。

    5、在父进程中,父进程关闭管道1的读出端和管道2的写入端;

    6、父子进程建立双向通信。

    虽然全双工管道两端都可读可写,但是两端同时写的时候数据是否会相互覆盖?下面用程序验证这个问题。

  1#include<unistd.h>
  2#include<stdio.h>
  3#include<sys/types.h>
  4#include<sys/socket.h>
  5#include<string.h>
  6#include<stdlib.h>
  7#include<sys/wait.h>
  8
  9 int main(){
 10     int fds1[2];
 11     int fds2[2];
 12     int pipefd[2];      //全双工管道
 13     char buff[BUFSIZ];
 14     memset(buff,0,sizeof(buff));
 15     int ret=pipe(fds1);
 16     if(ret){
 17          perror("pipe");
 18          return 1;
 19     }
 20     ret=pipe(fds2);
 21     if(ret){
 22          perror("pipe");
 23          return 1;
 24     }
 25    ret=socketpair(AF_UNIX,SOCK_STREAM,0,pipefd);
 26     if(ret){
 27          perror("pipe");
 28          return 1;
 29     }
 30
 31     int pid=fork();
 32     if(pid<0){
 33         printf("fork error");
 34         return 1;
 35     }
 36     else if(pid==0){
 37         close(pipefd[0]);
 38         close(fds1[1]);
 39         close(fds2[0]);
 40         char* data="child writesocketpair";
 41        write(pipefd[1],data,strlen(data));      //向全双工管道1端写数据
 42         write(fds2[1],"y",1);                   //子进程通过管道fds2写端写入y通知父进程:子进程已经写了socketpair的1端
 43         read(fds1[0],buff,BUFSIZ);             //读取fds1管道查看是否父进程写了socketpair的0端
 44         if(buff[0]=='y'){          
 45             memset(buff,0,sizeof(buff));
 46
 47            read(pipefd[1],buff,sizeof(buff));//获取socketpair的1端数据(该数据由父进程发送)
 48             printf("child get socketpair%s\n ",buff);
 49             exit(0);
 50         }
 51         else{
 52             printf("child not getsockerpair");
 53             exit(1);
 54         }
 55     }
 56     else{
 57         close(fds1[0]);
 58         close(fds2[1]);
 59         close(pipefd[1]);
 60         char* data="parent writesocketpair";
 61         write(pipefd[0],data,strlen(data));        //父进程写socketpair的0端
 62         write(fds1[1],"y",1);                      //父进程通过管道fds1写端写入y通知子进程:父进程已经写了socketpair的0端
 63         read(fds2[0],buff,BUFSIZ);                 //读取fds2管道查看子进程是否写了socketpair的1端
 64         if(buff[0]=='y'){
 65             memset(buff,0,sizeof(buff));
 66             read(pipefd[0],buff,sizeof(buff));   //读取socketpair的0端(该数据由子进程发送)
 67             printf("parent get socketpair%s\n",buff);
 68         
 69         }
 70         else{
 71             printf("child not getsocketpair");
 72             return 1;
 73         }
 74     }
 75     return 0;
 76 }

运行结果:

 

示例解析:父进程和子进程同时写进一个全双工管道socketpair并且都等待对方写完以后才读取数据。这里用了三个管道,一个全双工管道socketpair和两个半双工管道fds1,fds2,全双工管道用于父子进程同时读同时写,半双工管道用于父子进程通知对方自己已经写入了全双工管道。结果显示父子进程同时写的数据不会相互覆盖,可见全双工管道底层实现逻辑类似用两个半双工管道模拟的。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值