Linux入门--构建进程间通信的方案之管道(上)

文章目录

一、进程间通信介绍

1.进程间通信目的

2.进程间通信发展

3.进程间通信分类

二、管道

1.管道定义

1.1匿名管道

实例代码:从键盘读取数据写入管道,读取管道写到屏幕

2.用fork来共享管道原理

3.站在文件描述符角度深度理解管道

4.站在内核角度-管道本质

5.管道读写规则

6.管道特点

总结



一、进程间通信介绍

1.进程间通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种时间(如进程终止时要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2.进程间通信发展

  • 管道
  • System V 进程间通信
  • Posix 进程间通信

3.进程间通信分类

管道:匿名管道pipe 命名管道

System V IPC :消息队列 共享内存 信号量

POSIX IPC:消息队列 共享内存 信号量 互斥量 条件变量 读写锁

通信的本质是:让不同的进程看到同一份资源

二、管道

1.管道定义

  • 管道是Unix中最古老的进程间通信的方式
  • 将一个进程连接到另一个进程的一个数据流称为 -- 管道
  • 通常用来具有血缘关系的进程,常用于父子通信

创建管道: int pipefd[2] = {0}; //数组降维

                   int n =  int pipe (int pipefd[2]);

                   if(n <0) //创建出错

                   if(n == 0) //创建成功

其中pipefd[2]是一种输出型参数。创建管道时候要获得读端和写端,即需要读写的fd。创建管道成功,返回0,否则返回-1.

1.1匿名管道

#include<unistd.h>
//创建一无名管道
int pipe(int fd[2]); // fd:文件描述符组,fd[0] 表示读 fd[1] 表示写
返回值: 成功返回0 ,失败返回错误代码

实例代码:从键盘读取数据写入管道,读取管道写到屏幕

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
    int fds[2];
    char buf[100];
    int len;
    if ( pipe(fds) == -1 )
    perror("make pipe");
    exit(1);
    // read from stdin
    while ( fgets(buf, 100, stdin) ) {
    len = strlen(buf);
    // write into pipe
    if ( write(fds[1], buf, len) != len ) {
     perror("write to pipe");
     break;
   }
    memset(buf, 0x00, sizeof(buf));
 
   // read from pipe
   if ( (len=read(fds[0], buf, 100)) == -1 ) 
   {
        perror("read from pipe");
        break;
   }
   // write to stdout
   if ( write(1, buf, len) != len ) 
    {
       perror("write to stdout");
       break;
       }
     }
}

2.用fork来共享管道原理

fork成功,返回0,创建成功了子进程,返回-1,创建子进程失败

//任何一种通信,一定要首先保证不同的进程之间可以看到同一份资源

int main()
{
    //父进程创建管道
    int pipefd[2] = {0};   //pipefd[0] 读 pipefd[1] 写
    int n = pipe(pipefd[2]);  
    
    pid_t id = fork();
    int n = pipe(pipefd[0]);
    
    //创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0)
    {
        //创建了子进程 看到了同一份资源 :管道
        //子进程写入,关闭读端
        close(pipefd[0]);
        //开始通信 结合某种场景
        const std::string namestr = "hello,我是子进程";
        int cnt = 1;
        char buffer[1024]; //组合身份,cnt发送给父进程
        while(true)
        {   //将后面的数据打包,给buffer
            snprintf(buffer,sizeof(buffer), "%s,计数器:%d ,我的ID:%d\n",namestr.c_str(),cnt++,getpid());
            //往管道文件里写入 将buffer指向的内存写入strlen(buffer)个,写到pipefd[1]所指的文                  件内
            write(pipefd[1],buffer,strlen(buffer));
            sleep(1);
        }

        //通信结束,关闭写入端
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    char buffer[1024];
    while(true)
    {
        int n = read(pipefd[0],buffer,sizeof(buffer)-1); 
        if(n>0)
        {
            buffer[n] = '\0';
            std::cout<< "我是父进程,child give me message: " <<buffer<<std::endl;
        }
    }
    //关闭不需要的fd 父进程读 子进程写
    close(pipefd[1]);
}

3.站在文件描述符角度深度理解管道

首先,父进程创建出管道,占用两个文件描述符读和写,父进程fork出子进程,继承了父进程,此时有两组fd看到了同一份管道资源。然后,父进程关闭读/写文件描述符,子进程也关闭读/写文件描述符,然后开始在各自的进程中执行读/写操作,开始通信。

4.站在内核角度-管道本质

管道的本质和文件一样,使用和文件一致。

5.管道读写规则

  • 当没有数据可读或管道满了 ,进程处于阻塞状态
  • 当要写入的数据量小于PIPE_BUF时,linux将保证写入的原子性
  • 当写入的数据量小于PIPE_BUF时,linux将不能保证写入的原子性

6.管道特点

  • 单向通信
  • 管道的本质是文件,因为fd的生命周期随进程,管道的生命周期是随进程的
  • 父进程和子进程可以通过管道通信的本质:父进程和子进程有继承关系,可以看到同一份文件(管道通信通常用来具有血缘关系的进程,进行进程间通信,常用于父子通信)
  • pipe打开管道,并不清楚管道的名字,称为匿名管道
  • 在管道通信中,写入的次数和读取的次数不是严格匹配的(比如写100次,读50次)读写没有强相关 --面向字节流
  • 子进程write写慢点,发现父进程读取也变慢了-------------------
  • ①如果read读取完毕所有的管道数据,对方不发送,只能等待
  • ②子进程write写入变快,父进程每隔10s再进行读取--管道文件是有大小的 65535(2^16),写端将管道写满了,就不能再写了
  • ③管道具有一定的协同能力,读写按照一定的步骤进行通信--自带同步机制
  • ④ 关闭了写端,读端一直读--当读取完毕管道数据,再读read到0,表示父进程读到了文件结尾
  • ⑤写端一直写,读端关闭,没有意义,os不会维护无意义低效率的进程,此时os会kill一直在写入的进程(通过发送13号信号SIGPIPE 终止进程)
  • 管道是半双工的,数据只能一个方向流动,需要双方通信时,需要建立起两个管道

总结

猜你喜欢

转载自blog.csdn.net/jolly0514/article/details/132324488