进程间通信是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享内存、Socket、Streams等。其中Socket和Streams支持不同主机上的两个进程IPC。
一、管道
管道,通常指无名管道,是UNIX系统IPC最古老的形式。
1、特点
- 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
- 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
- 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
2、原型
#include <unistd.h>
int pipe(int pipefd[2]);
pipefd[2] 用于存放读和写的文件描述符 大小为2的数组
返回值:成功返回 0 失败返回-1 ,errno被设置
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开(巧记:我们说话时说01和读写,0对应读,1对应写)。如下图:
3、demo1
思路:
- pipe创建管道
- fork创建进程
- 父进程往管道里写,子进程读管道里的数据并打印
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int pid;
int fd[2];
char r_buf[128] = {0};
if ((pipe(fd)) == -1) { //创建管道
perror("pipe fail");
exit(-1);
}
if ((pid = fork()) == -1) { //创建进程
perror("fork fail");
exit(-1);
}
if (pid > 0) { //父进程
close(fd[0]); //关闭读端
write(fd[1], "hallo world!", strlen("hallo world!")); //往管道里写
close(fd[1]); //关闭管道
} else if (pid == 0) {
close(fd[1]); //关闭写端
read(fd[0], r_buf, 128); //读取管道中的数据
printf("%s\n", r_buf); //打印数据
close(fd[0]); //关闭管道
}
return 0;
}
运行结果:
二、FIFO
FIFO,也称为命名管道,它是一种文件类型。
1、特点
- FIFO可以在无关的进程之间交换数据,与无名管道不同。
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2、原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname 创建一个名字为"pathname"的FIFO特殊文件
mode 文件权限
返回值:成功返回 0 失败返回 -1 ,errno被设置
其中的mode参数与open函数中的mode相同。一旦创建了一个FIFO,就可以用一般的文件I/O函数操作它。当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
- 若没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开此FIFO。类似的,只写open要阻塞到某个其他进程为读而打开它。
- 若指定了O_NONBLOCK,则只读open立即返回,而只写open将出错返回 -1,如果没有进程已经为读而打开该FIFO,其errno置ENXIO。
3、demo
FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。下面的例子演示了使用FIFO进行IPC的过程。
思路:
- mkfifo创建FIFO
- fork创建进程
- 父进程只写打开FIFO,并往FIFO里写如数据
- 子进程只读打开FIFO,并读取FIFO里的数据并打印
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
int fd;
int pid;
int sum = 3;
char r_buf[128] = {0};
if (((mkfifo("file", 0600)) == -1) && (errno != EEXIST)) { //创建FIFO,如果FIFO存在条件为假
perror("mkfifo fail");
exit(-1);
}
if ((pid = fork()) == -1) { //创建进程
perror("fork fail");
exit(-1);
} else if (pid > 0) { //父进程
fd = open("file", O_WRONLY); //打开FIFO
while(sum) //间隔一秒往FIFO里写入数据
{
write(fd, "message form fifo", strlen("message from fifo"));
sleep(1);
sum--;
}
close(fd); //关闭FIFO
} else if (pid == 0) { //子进程
fd = open("file", O_RDONLY); //打开FIFO
while (sum) //间隔一秒读取一次FIFO里的数据并打印
{
read(fd, r_buf, 128);
puts(r_buf);
sleep(1);
sum--;
}
close(fd); //关闭FIFO
}
return 0;
}
运行结果:
三、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
1、特点
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2、原型
//头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
//创建或打开队列,成功返回队列ID,失败返回-1,errno被设置
int msgsnd(int msqid, const void *msgp, size_t msgsz, int flag);
//写入数据,成功返回0,失败返回-1, errno被设置
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int flag);
//读取数据,成功返回数据的字节数,失败返回-1, errno被设置
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//根据cmd(删除队列、设置数据元素、读取数据结构)命令执行操作。失败返回-1,errno被设置
key | 由ftok()生成,key为队列ID的组成部分 |
msgflg | 标志位(IPC_CREAT、IPC_EXCL、 IPC_NOWAIT)还应加上队列权限(读、写、执行) |
msqid | 队列ID |
msgp | 消息缓冲区指针(结构体指针) |
flag | 为0表示阻塞方式(队列空间不够就会堵塞),设置IPC_NOWAIT 表示非阻塞方式 |
msgsz | 消息数据长度,不含数据类型长度4个字节 |
msgtyp | 数据类型 type == 0 返回队列中的第一个消息。type > |