进程间通讯IPC
上一讲我们说到信号,进程间的整型数据可以通过信号进行传递,但是其他信号(例如结构体)信号就不够用了。而在进程中我们学到每个进程都有各自独立的地址空间,进程互相不能访问,所以进程间若想进行数据交换,就必须通过内核。在内核中开辟一块缓冲区,用于传递信号的写入和读出,这种机制称为进程间通信(IPC,InterProcess Communication)
什么是管道、管道的特点
管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
- 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
- 通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
- 管道单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道的实现机制:
管道是由内核管理的一个缓冲区。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
管道的创建——pipe函数
头文件<unistd.h>
功能:创建一无名管道
原型:int pipe(int file_descriptor[2]);
参数:
file_descriptor:文件描述符数组,其中file_descriptor[0]表示读端,file_descriptor[1]表示写端
返回值:
成功返回0,失败返回错误代码
创建成功后,在两个进程中,使用read函数和write函数进行读写操作。
特别注意read函数和write函数的第二个参数是void *
所以如果想用匿名管道传送结构体,记得取地址哦
这是在代码中使用管道的函数,另外一种在shell中使用管道,用“|”即可,这里不赘述。
代码实验
我们以父进程写hello rabbit!,子进程读出并打印hello rabbit!为例:
#include <iostream>
#include<unistd.h>//unix stand lib
#include<sys/types.h>
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<dirent.h>//file dir
#include <sys/wait.h>//wait func
#include <stdlib.h>//ststem
#include <signal.h>
#include <string.h>
using namespace std;
int main(int argc, char *argv[])
{
int pipe_fd[2];
int r_size = 0;
pid_t child_pid;
if (pipe(pipe_fd) == -1)
{
perror("pipe error:");
return -1;
}
//那么这里pipe就会打开2个文件描述符pipe_fd[0]表示读端
child_pid = fork();
if (child_pid == 0)//读
{
//子进程也继承了2个文件描述符
close(pipe_fd[1]);//子进程读端我们会关闭写端
char recv_buffer[50];
memset(recv_buffer, 0, sizeof(recv_buffer));
read(pipe_fd[0], recv_buffer, sizeof(recv_buffer));//希望读到buffer的所有字节
cout << "child recv buffer=" << recv_buffer << endl;
while (1)
{
sleep(1);//不让子进程结束
}
}
else if (child_pid > 0)//写
{
close(pipe_fd[0]);//父进程写端我们会关闭读端
char buffer[50] = { 0 };
strcpy(buffer, "hello rabbit!");
//如果发送的是字符串那么可以使用strlen函数
r_size = write(pipe_fd[1], buffer, strlen(buffer));
while (1)
{
sleep(1);//不让父进程结束
}
}
return 0;
}
打印结果:
代码不难,只需要注意一个点:
匿名管道是与fork互相依存的,也就是说pipe产生读写2个文件描述符后,通过fork复制到子进程,所以实际上父子各有2个文件描述符,一共是4个描述符,而管道是单向的,所以一个进程只会用到读/写一端,通常我们将另外一端close掉。
也就是说,一个进程若想又读又写,必须创建两条管道才可以。