进程之间通信手段有哪些?
就实现手段上来看,有以下几种:
(1) 匿名管道(有亲缘关系进程)
(2) 有名管道(无亲缘也可以)
(3) 消息队列
(4) 共享内存
(5) 信号量
(6) 文件(不推荐)
(7) socket套接字
我们来分析以下匿名管道和有名管道进行进程之间通信的方法:
匿名管道
匿名管道用于具有血缘关系之间的进程进行通信。
首先,说一下管道的特点:
1、内核缓冲区
2、非文件
3、不占用磁盘空间
4、两部分:读端和写端
5、默认是阻塞的
6、操作管道进程结束后,自动释放
7、半双工通信
8、默认缓冲区是 4k
匿名管道进行父子进程之间通信需要用到的函数:
1、int pipe(int fd[2])
该函数用来创建一个匿名管道,传输创建成功管道的读端和写端对应的文件描述符。
其中,fd[0] 为读端, fd[1]为写端。
通常,该函数和fork函数结合使用。
下面请看详细代码:
这段代码的意思是,通过pipe函数创建匿名管道之后。用fork函数进行子进程的创建。创建之后,在那一瞬间,父进程和子进程具有相同的文件描述符表。因此,在子进程中也存在一个文件描述符指向管道缓冲区。
因此可以进行通信。
关闭父进程的写端,关闭子进程的读端。就形成了下面的这个通信图。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <stdlib.h>
5
6 int main(void)
7 {
8 int fds[2];
9 if(pipe(fds)==-1)
10 perror("pipe"),exit(1);
11 pid_t pid=fork();
12 if(pid==-1) perror("fork"),exit(1);
13
14 if(pid==0){//子进程
15 close(fds[0]);//关闭读端
16 sleep(1);
17 write(fds[1],"abj",3);//在写端上写入abj
18 close(fds[1]);//再关闭写端
19 exit(0);
20 }else{//父进程
21 close(fds[1]);//关闭写端
22 char buf[100]={};
23 int r=read(fds[0],buf,100);//将管道中的数据读到buf中,返回值是实际读取的字节数
24 if(r==0)//读取的自己为0,代表读取文件结束
25 printf("read EOF\n");
26 else if(r==-1){
27 perror("read"),exit(1);
28 }
29 else if(r>0)
30 printf("buf=[%s]\n",buf);
31 close(fds[0]);//读取成功,关闭读端
32 exit(0);
33 }
34 }
35
需要注意的是:
1、如果写端关闭,读端未关闭。则读端会停止阻塞,立即返回读取字节数为 0 。
因此,可在程序中根据返回的是否是 0 来判断写端是否已经关闭。
2、如果读端关闭,写端还没有关闭。那么,写端继续写,操作系统会给写端进程发送一个SIGPIPE信号,终止写端程序。
管道默认是阻塞的,那么如何设置管道为非阻塞的呢?
通过下面代码给需要修改阻塞状态的文件描述符进行增加flag的操作。
int flags = fcntl(fd[0], F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);
有名管道
有名管道与匿名管道不同的地方是:在磁盘上有这样一个文件,作为标志。
其他属性和匿名管道基本一致。
创建方式
命令的方式:mkfifo 管道名
函数的方式:int mkfifo(char* path, mode_t mode);
其中,函数方式创建,当返回值是-1的时候,表示创建失败。
其他的就类似于文件的操作了。
下面看详细代码:
写端代码:(没有进行错误判断)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
void main()
{
int ret = access("/home/junkle/桌面/fifo.txt",F_OK);
if(ret != -1) {printf("error\n");return;}
int r = mkfifo("/home/junkle/桌面/fifo.txt", 0664);
printf("successfully\n");
int fd = open("/home/junkle/桌面/fifo.txt", O_WRONLY);
char* pp = "hello world";
while(1)
{
sleep(1);
write(fd, pp, strlen(pp) + 1);
printf("haccccccccccccc\n");
}
close(fd);
}
读端代码:
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
void main()
{
int ret = access("/home/junkle/桌面/fifo.txt",F_OK);
if(ret == -1) {printf("error\n");return;}
printf("successfully\n");
int fd = open("/home/junkle/桌面/fifo.txt", O_RDONLY);
char pp[512] = {};
while(1)
{
int num = read(fd, pp, 512);
if(num == 0){break;}
printf("buff = %s\n", pp);
}
close(fd);
printf("finished\n");
}
可以看的出,基本和文件操作一致。
但是,管道更适合进程之间进行通信。因为其本身自带阻塞属性,文件必须等写端写入数据,读端才可以进行数据读取。
需要注意的一点是,在程序中我们尽量让写端先进行关闭,读端通过返回是否为0判断写端是否关闭了。否则,如果读端全部关闭之后,写端未关闭,操作系统会发送SIGPIPE信号给档当前进行强制关闭程序,这不是我们想要的。