进程间通信 - 管道

时间:2021-05-09 19:08:35
进程间通信介绍

进程间通信的条件 -  进程独立
进程间通信的目的
  • 数据传输:一个进程需要将他的数据发送给另一个进程
  • 资源共享:多个进程之前共享同样的资源
  • 通知事件:一个进程需要向另一个或者一组进程发送消息,通知他们发生了某种事件
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时通知他们状态改变
进程间通信的分类
  • 管道
匿名管道 pipe
命名管道
  • System V IPC
System V 消息队列
System V 共享内存
System V 信号量
  • POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁

管道

什么是管道
  • 管道其实是内核提供的一段内存
  • 我们把一个进程连接带另一个进程的数据流称为管道

下面以 who | wc -l 为例

进程间通信 - 管道

匿名管道

创建一个无名管道

#include <unistd.h>

int pipe(int fd[2]);

参数:
fd:文件描述符组,fd[0]表示读端(像一个嘴巴),fd[1]表示写端(像一支笔)
返回值:
成功返回0,失败返回错误码
创建失败的原因:
1.内存不足
2.没有对应的文件描述符

用fork实现共享管道的原理

     进程间通信 - 管道

每一个进程都有一个读管道的文件描述符和写管道的文件描述符,我们在特定场合可以关掉相应的文件描述符,比如执行父进程写入管道,子进程从管道中获取数据时,可以将父进程的读端关掉,将子进程的写端关掉。

在文件描述符的角度理解管道

进程间通信 - 管道

同上面一样,子进程继承了父进程,那么子进程的文件描述符继承了父进程,进程的3号和4号文件描述符分别是作为管道的读端和写端,执行指定操作时,可以关掉不用的端口。

其实,看待管道,就如同看待文件一样,使用方法都相同(在pipe的前提下),有read,wirte,open,close一系列操作,迎合了Linux“一切皆文件”的思想。

管道读写规则

  • 如果管道所有的写端都关闭,那么read返回 0
  • 如果所有读端都关闭,执行write 时会产生 SIGPIPE 信号,导致write进城退出,进程异常终止

匿名管道特点

  • 匿名管道只能用于有亲缘(fork之后的父子进程、多次fork以后的兄弟进程)关系的进程之间进行通信,通过继承文件描述符的方式,获取管道的读取端
  • 管道的生命周期随进程,进程结束以后,相应的管道也会自动释放。(类似于malloc)
  • 管道内有同步互斥机制
  • 管道是面向字节流的通信,比如一次写入了10k的数据,读取时一次可以读10k,也可以一次读1k,读10次
  • 管道是半双工通信,可以读,可以写,但是不能同时读写,也就是如果要进行双向通信,需要建立两个管道

创建一个管道,完成父进程向里面写数据,子进程从里面读取数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main()
{
    int fd[2];
    if(pipe(fd) < 0)
    {
        perror("pipe fd1");
        exit(1);
    }
    pid_t pid = fork();
    while(1)
    {
        if(pid > 0) //parent
        {
            char buf[128];
            int read_size = read(0,buf,sizeof(buf) -1);
            write(fd[1],buf,read_size+1);
            sleep(1);
        }
        else if(pid == 0) //child
        {
            char buf[128];
            read(fd[0],buf,128);
            printf("child read : %s",buf);
            sleep(1);
        }
    }
    return 0;
}

命名管道                                                                            

命名管道也是一个文件,他的存在是为了解决管道只能咋有亲缘关系的进程中进行通信的问题。

创建一个命名管道

  • 从命令行上创建
mkfifo filename
  • 用相关函数从程序中创建
int mkfifo(const char* filename,mode_t mode);

命名管道和匿名管道,除了创建与打开方式不同,其余操作相同,具有的意义也相同。

用命名管道实现service & client通信

//servicePipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>


int main()
{
    //1.创建一个命名管道
    if(mkfifo("./mypipe",0666) < 0)
    {
        perror("mkfifo");
        exit(1);
    }
    //2.打开管道
    int rfd;
    if((rfd = open("mypipe",O_RDONLY)) < 0)
    {
        perror("open mypipe");
        exit(1);
    }
    //3.循环的从管道里读数据
    char buf[128] = {0};
    while(1)
    {
        printf("wait...\n");
        int read_size = read(rfd,buf,sizeof(buf)-1);
        if(read_size > 0)
        {// 服务器读取到管道中有数据
            buf[read_size] = 0;
            printf("client say : %s",buf);
        }
        else if(read_size == 0)
        {// 客户端已经退出管道
            printf("client say down!\n");
            exit(1);
        }
        else
        {// 读取错误
            perror("read");
            exit(1);
        }
    }
    return 0;
}
//clientPipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main()
{
    int wfd = open("mypipe",O_RDWR);
    if(wfd < 0)
    {
        perror("client open");
        exit(1);
    }
    char buf[128] = {0};
    while(1)
    {
        int read_size = read(0,buf,sizeof(buf)-1);
        if(read_size > 0)
        {
            buf[read_size] = 0;
            write(wfd,buf,strlen(buf));
        }
        else
        {
            exit(1);
        }
    }
    return 0;
}
//Makefile
.PHONY:all
all: client service

service: servicePipe.c
	gcc -o $@ $^

client: clientPipe.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm service client

运行service -> service创建命名管道(mypipe) -> client给管道中写数据 -> service从管道中读数据

运行结果展示

编译代码,生成两个可执行程序,service和client

进程间通信 - 管道

开启两个终端分别运行两个可执行程序

client往管道里写数据:

进程间通信 - 管道

service从管道中读数据:

进程间通信 - 管道

命名管道的特点

  • 命名管道可以用于任意两个进程之间的通信
  • 管道的生命周期随进程,进程结束以后,相应的管道也会自动释放。(类似于malloc)
  • 管道内有同步互斥机制
  • 管道是面向字节流的通信,比如一次写入了10k的数据,读取时一次可以读10k,也可以一次读1k,读10次
  • 管道是半双工通信,可以读,可以写,但是不能同时读写,也就是如果要进行双向通信,需要建立两个管道
以上命名管道的特点,除了第一点,其余都和匿名管道相同。