进程间通信------管道

更多linux知识:linux目录索引


1.什么是管道

  把一个进程连接到另一个进程的一个数据流称为一个“管道”,通常是用作把一个进程的输出通过管道连接到另一个进程的输入。管道本质上是内核的一块缓存

例子:

  在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。

2. 管道的实现原理

这里写图片描述


3. 匿名管道

  1. 概念

       匿名管道是基于文件描述符的通信方式。实现两个进程间的通信时必须通过fork创建子进程,实现父子进程之间的通信

  2. 读写规则

    1. 管道内没有数据时,读端(read)发生阻塞,等待有效数据进行读取
    2. 管道容量被数据填满时,写端(write)发生阻塞,等待进程将数据读走再进行写入
    3. 如果所有管道写端对应的文件描述符被关闭,read返回0,但会将之前管道里的数据读完
    4. 如果所有管道的读端对应的文件描述符被关闭,write操作会产生信号,SIGPIPE,进而导致write进程退出
    5. 当要写入的数据量不大于管道的容量(PIPE_BUF)时,linux将保证写入的原子性
    6. 当要写入的数据量大于管道容量(PIPE_BUF)时,linux将不再保证写入的原子性
  3. 特点

    1. 只能够进行单向通信
    2. 只能够用于有血缘关系(父子,兄弟,爷孙)的进程之间,多常用于父子之间
    3. 管道内部自带同步机制:子进程写一条,父进程读一条
    4. 管道在进行通信的时候,对外层提供的服务叫做面向字节流的服务
    5. 当进程退出之时,管道也随之释放,与文件保持一致
    6. 管道的生命周期为随进程,进程结束管道就没了
      流:相当于水流,写入数据时,写多少字节和你自己有关系,读的时候没有 格式的要求,完全取决你自己;
      字节流:以字节来读取和写入,字节数的大小完全取决于自己
  4. 单进程通信原理

这里写图片描述

  1. 父子进程通信原理

    这里写图片描述

  2. 创建管道

     #include <unistd.h>
     int pipe(int pipefd[2]);
     参数:fd为文件描述符数组,其中fd[0]表示读端,fd[1]
          表示写端
    返回值:成功返回0,失败返回-1
    
  3. 单进程通信实例

    代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    int main()
    {
        int fd[2];//文件描述符数组
        char buf[1024];//临时缓冲区
        int len;
        ssize_t s;
        ssize_t w;
        if(pipe(fd) == -1){//创建管道
             perror("pipe");
             return -1;
        }
    
         while(1){
             printf("Please enter:");
             fflush(stdout);
              s = read(0,buf,sizeof(buf));//从屏幕上读取内容
              if( s < 0){
                     perror("read");
                     return -2;
                }
                len = strlen(buf);
    
                 w = write(fd[1],buf,len);//将读取的数据写入管道
                if(w != len){
                        perror("write");
                        return -3;
                }
    
    
               memset(buf,0,sizeof(buf));//将buf清0
    
                s = read(fd[0],buf,sizeof(buf));//从管道当中读取数据
                if(s < 0){
                        perror("read");
                        return -4;
                        }
    
                w = write(1,buf,len);//将读的数据写入屏幕
                if(w != len){
                        perror("write");
                        return -5;
                    }
             }
    
            return 0;
        }
    

    结果:

    这里写图片描述

  4. 父子进程通信实例

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    int main()
    {
            int fds[2];
            if(pipe(fds) < 0){//创建一个管道,用于父子间进行通信
                     perror("pipe");
                     return 1;
            }
            char buf[1024];//临时数组,用于存放通信的消息
    
            printf("Please enter:");
            fflush(stdout);
            ssize_t s =  read(0,buf,sizeof(buf)-1);
            if(s > 0){
                buf[s] = 0;
            }
    
            pid_t pid = fork();
            if(pid == 0){//子进程只写,关闭读端
                    close(fds[0]);
                    while(1){
                            sleep(1);
                            write(fds[1],buf,strlen(buf));//将buf的内容写入管道
                    }
            }
            else{//父进程只读,关闭写端
                  close(fds[1]);
                  char buf1[1024];
                  while(1){
                          ssize_t s = read(fds[0],buf1,sizeof(buf1)-1);//从管道里读数据,放入buf
                           if(s > 0){
                                    buf1[s-1] = 0;
                                    printf("client->farther:%s\n",buf1);
                           }
                   }
          }
    }
    

    结果:

    这里写图片描述


4. 命名管道

  1. 概念
       命名管道本质上是一个管道文件,可以通过命令创建也可以通过函数创建,用户可以看到

  2. 特点

      1. 可以进行不相干进程间的通信

      2. 命名管道是一个文件,对于文件的相关操作对其同样适用

  3. 读写规则

      1. 对于管道文件,当前进程操作为只读时,则进行阻塞,直至有进程对其写入数据

      2. 对于管道文件,当前进程操作为只写时,则进行阻塞,直至有进程从管道中读取数据

  4. 命名管道的创建

     1. 利用命令创建

            mkfifo filename
    

     2. 函数创建

            int mkfifo(const char *filename,mode_t mode);
    
            【参数】:
                filename:创建的有名管道的全路径名
                mode:创建的命名管道的模式,指明其存取权限
    
  5. 文件拷贝实例

    write.c

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    //读取文件,将文件内容写入管道
    int main()
    {
         mkfifo("tp",0644);//创建一个管道文件
         int  infd = open("123",O_RDONLY);//打开一个文件
         if(infd == -1){
                     perror("open");
                      return 1;    
        }
    
         int outfd = open("tp",O_WRONLY);//打开管道文件,将123文件的内容写入管道文件
        if(outfd == -1){
                    perror("open");
                     return 2;
        }
    
         char buf[1024];//用于存放文件内容
         ssize_t s;
    
         while( (s = read(infd,buf,sizeof(buf))) >0)
         {
                     write(outfd,buf,s);
         }
         close(infd);
         close(outfd);
    
    }
    

    read.c

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    //从管道文件里面读取内容,并将内容写入另一个文件中
    int main()
    {
           int infd = open("abc.bak",O_CREAT | O_WRONLY | O_TRUNC,0644);//创建一个新的文件
           if(infd == -1){
                   perror("open");
                   return 1;
           }
    
            //将从管道读取的内容写入到新的文件中
    
            int outfd = open("tp",O_RDONLY);//打开管道文件
            if(outfd == -1){
                    perror("open");
                    return 2;
            }
    
            char buf[1024];//临时数组
            ssize_t s;
    
            while( (s = read(outfd,buf,sizeof(buf))) > 0)
            {
                write(infd,buf,s);
            }
            close(infd);
            close(outfd);
    
            return 0;
    }
    

    这里写图片描述

  6. 服务器、客户端实例

    server.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
//
int main()
{
    umask(0);//将权限清0
    if(mkfifo("./mypipe",0666|S_IFIFO) < 0){//创建管道
        perror("mkfifo");
        return 1;
    }

    int fd = open("./mypipe",O_RDONLY);//打开管道
    if(fd < 0){
        perror("open");
        return 2;
    }

    char buf[1024];
    while(1){
        buf[0] = 0;
        printf("请等待。。。\n");
        ssize_t s = read(fd,buf,sizeof(buf)-1);

        if(s > 0){
            buf[s-1] = 0;//过滤\n
            printf("服务器:%s\n",buf);
        }else if(s == 0){//当客户端退出时,read返回0
            printf("客户端退出,自己退出\n");
            break;
        }
    }
    close(fd);
    return 0;
}

client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int main()
{

    int fd = open("./mypipe",O_WRONLY);//打开管道
    if(fd < 0){
        perror("open");
        return 1;
    }

    char buf[1024];
    while(1){
        printf("客户端:");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);//向管道文件中写数据
        if(s > 0){
            buf[s] = 0;//以字符串的形式写
            write(fd,buf,strlen(buf));
        }
    }
    close(fd);
    return 0;
}

Makefile:

.PHONY:all
all:client server
client:client.c 
    gcc -o $@ $^

server:server.c 
    gcc -o $@ $^

.PHONY:clean
clean:
    rm -f client server mypipe

结果:

这里写图片描述


5.总结

类型 进程关系 不同点 本质
匿名管道 必须是亲缘关系 由pipe创建并打开 内核的一块缓存
命名管道 两个毫不相干进程 由mkfifo创建,open打开 一个文件

猜你喜欢

转载自blog.csdn.net/zhangye3017/article/details/80189861