进程间通信
之前进程之间交换信息的方法只能是经由fork或exec传送打开文件,或者通过文件系统。
下来将说明进程之间相互通信的其他技术—IPC(interProcess Commumication)
进程间通信IPC
IPC引入
IPC的方式通常有管道(包括无名管道和命名管道),消息队列,信号量,共享存储,Socket,Streams等,其中Socket和Streams支持不同主机上的两个进程IPC
一,管道
1.特点
- 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
- 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
- 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read,write等函数,但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中
2原理
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。管道的建立存在与内核之中,如下图:
要关闭管道只需将这两个文件描述符关闭即可
3创建
单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
创建管道
#include <unistd.h>
int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
#include<stdio.h>
#include <unistd.h>
#include <string.h>
//int pipe(int pipefd[2]);
//int pipe(int fd[2]);
int main()
{
int pid;
char buf[128];
int fd[2];
if(pipe(fd) == -1)//建立管道
{
printf("pipe create failed\n");
}
pid = fork();//建立子进程
if(pid < 0)//建立子进程失败
{
printf("the child create failed\n");
}
else if(pid > 0)//父进程运行空间,pid>0时
{
printf("this is father\n");
close(fd[0]);//父进程关闭读端文件描述符
write(fd[1], "hello world", strlen("hello world\n"));//父进程对管道内写入
}
else if(pid == 0)//子进程运行空间,pid=0时
{
printf("this is child\n");
close(fd[1]);//子进程关闭写端文件描述符
read(fd[0], buf, 128);//子进程从管道内读出数据到buf
printf("%s\n", buf);
}
return 0;
}
结果如下
FIFO 进程间通信第二种方式
特点
(1)FIFO可以在无关的进程之间交换数据,与无名管道不同。
(2)FIFO 有路径名(无名管道没有)与之相关联,它以一种特殊设备文件形式存在于文件系统中。
(3)管道中的数据被读走就没了,同时保持FIFO(先进先出的特点。
原理
与无名管道类似。
创建
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);// 返回值:成功返回0,出错返回-1
pathname是函数的名字
其中的 mode 参数与open函数中的 mode 相同(0600-读写)(open函数)。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,==是否设置非阻塞标志(O_NONBLOCK)==的区别:
若没有指定O_NONBLOCK(默认)
只读open 要阻塞到某个其他进程为写而打开此 FIFO。
只写open 要阻塞到某个其他进程为读**而打开它。
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO
实例
read.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
char buf[128];
if((mkfifo("./file",0600)==-1) && errno!=EEXIST)//建立管道
{
printf("mkfifo failure\n");
perror("why");
}
else if(errno==EEXIST)
{
printf("file eexist\n");
}
else
{
printf("mkfifo successed\n");
}
int fd = open("./file", O_RDONLY);
//打开管道,O_RDINLY-只读模式打开,如果没有另一个进程以只写模式打开管道,程序会阻塞此处
int nread = read(fd, buf, 20);//从管道内读数据
printf("read %d byte from fifo,context:%s\n",nread,buf);
close(fd);//关闭管道
return 0;
}
write.c
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
// ssize_t read(int fd, void *buf, size_t count);
char *buf="hello world !!!!!!!!";
int fd = open("./file",O_WRONLY);
//打开管道,O_WRONLY-只写模式打开,如果没有另一个进程读它,程序会阻塞此处
printf("write file success\n");
write(fd,buf,strlen(buf));写入数据
return 0;
}
~
读的时候 不写那么读就会阻塞,知道写执行,才会读
上述例子可以扩展成 客户进程—服务器进程 通信的实例,
write的作用类似于客户端,可以打开多个客户端向一个服务器发送请求信息,
read类似于服务器,它适时监控着FIFO的读端,当有数据时,读出并进行处理,
每一个客户端必须预先知道服务器提供的FIFO接口,如下图。
消息队列的通信原理
消息队列(message queue)
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点
(1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级(链表存放的为结构体)。
(2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除(管道是读完就消失),除非销毁队列。
(3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取(链表的特性),也可以按消息的类型读取。
(4)没有固定的读端与写端,
双方进程都可以
原理
创建
了解了原理,我们则发现主要关心两个问题。
问题一:进程A如何添加消息队列
问题二:进程B如何读取消息队列
常用的api
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag)