• 《Linux应用进程间通信(一) — 匿名管道》


    各个通信机制以及优缺点:https://www.cnblogs.com/wuyepeng/p/9747515.html

    1.进程间通信的概念

      每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

      不同进程间的通信本质:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同,而 pipe就是提供这份公共资源的形式的一种。

    2. 匿名管道

    2.1 管道的创建  

      管道是由调用pipe函数来创建:

    #include <unistd.h>
    int pipe (int fd[2]);
    
    参数:
        fd返回两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端。
    
    返回值:                       
        返回:成功返回0,出错返回-1 

    2.2 管道如何实现进程间的通信

    (1)父进程创建管道,得到两个件描述符指向管道的两端

    (2)父进程fork出子进程,子进程也有两个文件描述符指向同管道。

    (3)父进程关闭fd[0],子进程关闭fd[1],即子进程关闭管道读端,父进程关闭管道写端(因为管道只支持单向通信)。子进程可以往管道中写,父进程可以从管道中读,管道是由环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

    2.3 如何利用代码实现管道通信

    #include<stdio.h>
    #include<unistd.h>
    #include<string.h>
    #include<errno.h>
    int main()
    {
        int fd[2];
        int ret=pipe(fd);
        if(ret==-1)
        {
            perror("pipe error
    ");
            return -1;
        }
        pid_t id=fork();
        if(id==0)
        {
            int i=0;
            close(fd[0]);
            char* child="I am child!";
            while(i<5)
            {
                write(fd[1],child,strlen(child)+1);
                sleep(2);
                i++;
            }
        }
        else if(id>0)
        {
            close(fd[1]);
            char msg[100];
            int j=0;
            while(j<5)
            {
                memset(msg,'',sizeof(msg));
                ssize_t s=read(fd[0],msg,sizeof(msg));
                if(s>0)
                {
                    msg[s-1]='';
                }
                printf("%s
    ",msg);
                j++;
            }
        }
        else
        {
            perror("fork error
    ");
            return -1;
        }
        return 0;
    }
    

      运行结果:每隔2秒打印一次I am child! 并且打印了五次。

      

    2.4 管道读取数据的四种的情况

    (1)读端不读(fd[0]未关闭),写端一直写 

     (2)写端不写(fd[1]未关闭),但是读端一直读

    (3)读端一直读,且fd[0]保持打开,而写端写了一部分数据不写了,并且关闭fd[1]

       如果一个管道读端一直在读数据,而管道写端的引⽤计数⼤于0决定管道是否会堵塞,引用计数大于0,只读不写会导致管道堵塞。

    (4)读端读了一部分数据,不读了且关闭fd[0],写端一直在写且f[1]还保持打开状态

    #include<stdio.h>
    #include<unistd.h>
    #include<string.h>
    #include<errno.h>
    int main()
    {
        int fd[2];
        int ret=pipe(fd);
        if(ret==-1)
        {
            perror("pipe error
    ");
            return -1;
        }
        pid_t id=fork();
        if(id==0)
        {
            int i=0;
            close(fd[0]);
            char *child="I am child!";
            while(i<10)
            {
                write(fd[1],child,strlen(child)+1);
                sleep(2);
                i++;
            }
        }
        else if(id>0)
        {
            close(fd[1]);
            char msg[100];
            int status=0;
            int j=0;
            while(j<5)
            {
                memset(msg,'',sizeof(msg));
                ssize_t s=read(fd[0],msg,sizeof(msg));
                if(s>0)
                {
                    msg[s-1]='';
                }
                printf("%s %d
    ",msg,j);
                j++;
            }
            //写方还在继续,而读方已经关闭它的读端 
            close(fd[0]);
            pid_t ret=waitpid(id,&status,0);
            printf("exitsingle(%d),exit(%d)
    ",status&0xff,(status>>8)&0xff);
            //低八位存放该子进程退出时是否收到信号 
            //此低八位子进程正常退出时,退出码是多少
        }
        else
        {
            perror("fork error
    ");
            return -1;
        }
        return 0;
    }
    

      运行结果:

      

       使用kill -l 查看13号信号,可以知道13号信号代表SIGPIPE。

      

    总结:
      如果一个管道的写端一直在写,而读端的引用计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只写不读再次调用write会导致管道堵塞;
      如果一个管道的读端一直在读,而写端的引用计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只读不写再次调用read会导致管道堵塞;
      而当他们的引用计数等于0时,只写不读会导致写端的进程收到一个SIGPIPE信号,导致进程终止,只写不读会导致read返回0,就像读到件末尾样。

    2.5 管道特点

      1.管道只允许具有血缘关系的进程间通信,如父子进程间的通信。

      2.管道只允许单向通信。

      3.管道内部保证同步机制,从而保证访问数据的一致性。

      4.面向字节流

      5.管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。

    2.6 管道容量大小

    测试管道容量大小只需要将写端一直写,读端不读且不关闭fd[0],即可。 
    测试代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h>
    int main()
    {
        int fd[2];
        int ret = pipe(fd);
        if (ret == -1)
        {
            perror("pipe error
    ");
            return -1;
        }
        pid_t id = fork();
        if (id == 0)
        {//child
            int i = 0;
            close(fd[0]);
            char *child = "I am  child!";
            while (++i)
            {
                printf("pipe capacity: %d
    ", i*(strlen(child) + 1));
              //printf要写在write前面否则会因为write写满了而阻塞就不会进行下面的代码了,会使得输出计算少一次
                write(fd[1], child, strlen(child) + 1);
                
            }
            close(fd[1]);
        }
        else if (id>0)
        {//father
            close(fd[1]);//父进程的读端不能关闭,如果关闭了子进程写端会因为异常而退出
            waitpid(id, NULL, 0);
        }
        else
        {//error
            perror("fork error
    ");
            return -1;
        }
        return  0;
    }   
    

      可以看到写到65520之后管道堵塞了,而65536即为64K大小即为管道的容量

      原理是:我们写端每次写入的数据大小是13,统计我们可以进行多少次写入,写入次数*13就是管道容量,因为65533+13=65546>65536所以就不能继续输入了,有因为内存对齐问题,所以我们可以知道容量一定是64k

      

  • 相关阅读:
    转:VS2017常用快快捷键
    转:SQL Server中常用的快捷键
    转:SQL server中转换大小写快捷键
    转:left join 和 left outer join 的区别
    如何修改SVN的地址
    转 Echars地图详解
    HTML5 -- 使用css3实现简单的响应式布局
    Mac上用终端管理MySQL
    DDL 语句
    python 快速写作技巧,格式
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/13141456.html
Copyright © 2020-2023  润新知