进程间通信(IPC)篇
一、进程间通信概述
进程间通信是指在不同进程之间传播或交换信息,IPC的方式常有管道(包括无名管道和命名管道)消息队列,信号量,共享存储,Socket,Stream等。其中Socket和Stream支持不同主机上的两个进程IPC。
单机通信 | 多机通信 |
---|---|
半双工管道(FIFO) | 套接字 |
全双工管道 | STPEAMS |
消息队列 | |
信号量 | |
共享存储 |
二、半双工管道
管道是一个流,读走数据后,这个数据就不在缓存中了
PIPE(无名管道 ):
管道,通常指 无名管道 ,是UNIX系统IPC最古老的形式。只存在于内存中,当父子进程退出后,管道就消失了。
1.特点:
-
它是 半双工 的(即数据只能在一个方向上流动),具有固定的读端和写端。
-
它只是用于具有 亲缘关系 的进程之间 通信(也是父子进程或者兄弟进程之间【两个由同一个父进程创建的子进程】)
-
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read,write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且 只存在于内存中 。当父子进程退出后,管道就消失了。
2.原型:
#include<unistd.h>
int pipe(int fd[2]); //返回值 :若成功返回0 失败返回-1
3.返回值: 若成功返回0 失败返回-1
当一个管道建立时候,它会创建两个文件描述符: fd[0] 为读而打开 ,fd[1] 为写而打开。
pipefd[2]:文件描述符数组,fd[0]表示读端,fd[1]表示写端
read(fd[0] , buff , 128);
write(fd[1] , “hello world \n” , 12);
要关闭管道,只需要将这两个文件描述符关闭即可。
close(fd[0]); //关闭读端
close(fd[1]); //关闭写端
例如:单个进程中的管道几乎没有任何用处。所以,通常调用pipe的进程接着调用fork,这样就创建了父进程与子进程之间的IPC通道,
2. 管道 编程的实战:
无名管道:父进程往管道里写,子进程从管道外读:
父——>子的管道,父亲要发送数据,所以要关闭读端,儿子要接收数据,所以要关闭写端。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{
int fd [2]; //两个文件描述符
int pid;
char buff [128];
if(pipe(fd) == -1){
//创建管道
printf("Create pipe error! \n"); //创建管道错误
}
pid = fork(); //创建子进程
if(pid <0 ){
printf("Fork error \n"); //fork错误
}else if(pid > 0){
//父进程
sleep(3);
printf("this is father\n");
close(fd[0]); //关闭读端
write(fd[1],"hello from father\n",strlen("hello from father"));
wait();
}else{
printf("this is child\n");
close(fd[1]); //关闭写端
read(fd[0],buff,128);
printf("read from father:%s\n",buff);
exit(0);
}
return 0;
}
父进程等待3秒写入的时候,发现read 没读到数据 ,read会阻塞,等到父进程写入后,子进程执行read读出数据,
通过测试结果可以发现,一开始是父亲先输入信息,原因是儿子一开始是先read(),而read()函数是一个阻塞函数,当读到信息的时候才结束,没读到数据就阻塞。儿子收到信息后,儿子就执行完read(),并接着执行write(),同时父亲发完信息后也就是执行完write(),并接着执行read(),等待儿子发信息。就这样循环反复的收发信息,就实现了父子进程间的通信。
三、FIFO (命名管道)
FIFO,也称为命名管道,它是一种文件类型。
命名管道是一种特殊类型的文件,它以文件的形式存在,但不是真正的文件,并且不保留数据,只是起到一个数据传输的作用。
1.特点:
-
FIFO可以在无关的进程之间交换数据,与无名管道不同。
-
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2.原型:
#include<sys/stat.h>
#include<sys/types.h>
//返回值:成功返回0 ,失败返回-1
int mkfifo(char *pathname , mode_t mode);
返回值:成功返回0 ,失败返回-1
创建命名管道代码:
int mkfifo("./file",0600);
参数说明:
pathname:文件路径
mode:文件权限
mode与open函数中的mode相同。一旦创建一个FIFO,就可以用一般的文件I/O函数操作它(如 open ,read ,write)。
1. 打开命名管道文件FIFO
1、FIFO和通过pipe调用创建的管道不同,它是一个有名字的文件而表示一个打开的文件描述符。
2、在对它进行读或写操作之前必须先打开它。
3、FIFO文件也要用open和close函数来打开或关闭,除了一些额外的功能外,整个操作过程与文件操作是一样的。
4、传递给open调用的是一个FIFO文件的路径名,而不是一个正常文件的路径名。
2. 创建FIFO有名管道代码:
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<errno.h>
// int mkfifo(const char *pathname ,mode_t mode);
int main(){
if((mkfifo("./file",0600)== -1)&& errno!=EEXIST){
printf("mkfifo failuer\n");
perror("why");
}
return 0;
}
管道读取:
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
// int mkfifo(const char *pathname ,mode_t mode);
int main(){
if((mkfifo("./file",0600)== -1)&& errno!=EEXIST){
printf("mkfifo failuer\n");
perror("why");
}
int fd = open("./file",O_RDONLY); //O_RDONLY读数据
printf("open success\n");
return 0;
}
结果显示:
创建一个管道后, 打开管道后printf(“open success\n”); 没有执行。
3.命名管道的打开规则
1、如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志,即只设置了O_RDONLY),反之,如果当前打开操作没有设置了非阻塞标志,即O_NONBLOCK,则返回成功
2、如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
3、通俗的说,要打开一个FIFO命名管道文件,需要一个进程以写打开,并且另一个进程要以读打开,只有满足了有读打开和有写打开,命名管道才算打开成功。就像水管一样,水管的进口和出口需要同时打开,水才能流过去。
4. 当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别?
-
若没有指定O_NONBLOCK(默认),只读open要阻塞 到某个 其他进程为写而打开此FIFO ,类似的,只写open要阻塞到某个其他他进程为读而打开它。
-
若指定了O_NOBLOCK,则只读open立即返回。而只写open将出错误返回-1 如果没有进程已经为读而打开该FIFO,其errno置ENXIO 。
所以说上面代码堵塞,再一个进程用写的方式打开管道,就不会堵塞了
3. 命名管道的数据通信编程实现:
读的方式打开管道:
./read
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
// int mkfifo(const char *pathname ,mode_t mode);
int main(){
int buf[30]={
0};
if((mkfifo("./file",0600)== -1)&& errno!=EEXIST){
//创建管道
printf("mkfifo failuer\n");
perror("why");
}
int fd = open("./file",O_RDONLY); //O_RDONLY 读数据
printf("read open success\n");
return 0;
}
写的方式打开管道:
./write
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
int main(){
int fd = open("./file",O_WRONLY); //O_WRONLY 写数据
printf("write open success\n");
return 0;
}
当用读的方式打开 会堵塞, 再用写的方式打开,就不会阻塞,就像水管两头打开流通。
在./read堵塞的时候,在执行./write ,就可以获取数据
也可以实现两个进程间通信:多次写入:
./read
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<errno.h>
#include<fcntl.h>
int main(){
int buf[30]={
0};
int nread=0;
int fd = open("./file",O_RDONLY);
printf("read open success\n");
while(1){
nread = read(fd,buf,30);
printf(