进程间通信
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。进程间通信的方式有如下几种:
1.管道通信
特点:
-
管道只允许具有血缘关系的进程间通信,如父子进程间的通信。
-
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
-
管道并非是进程所有的资源,而是和套接字一样,归操作系统所有。可以将它看成文件系统,但该文件系统只存在于内存当中。
原型
#include <unistd.h>
/* Create a one-way communication channel (pipe).
If successful, two file descriptors are stored in PIPEDES;
bytes written on PIPEDES[1] can be read from PIPEDES[0].
Returns 0 if successful, -1 if not. */
extern int pipe (int fd[2]) __THROW __wur;
参数的说明:
字符数组fd是管道传输或者接收时用到的文件描述符,其中fd[0]
是接收的时候使用的文件描述符,即管道出口;而fd[1]
是传输的时候用到的文件描述符,即管道入口。
为了使数据可以双向传递,可以使用两个管道,一个管道负责进程1的写和进程2的读,另一个管道负责一个进程1的读和进程2的写。测试程序如下:
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUF_SIZE 30
int main(){
int fds1[2], fds2[2];
/*
*注意:
* 此处不能写char* str1 = "Who are you?";
* 得到的sizeof(str1)等于8,该值实际上是指针的大小,并不是字符串数组的大小
* 这个时候要用strlen()函数(strlen的唯一标准是找‘\0’)
* 系统函数返回值是char *(#include <string.h>)类型的往往会在末尾加上'\0'。
* 要注意的是未初始化的情况下,用strlen是不可行的
**/
char str1[] = "Who are you?";
char str2[] = "Thank you for your message.";
char buf[BUF_SIZE];
pipe(fds1); //创建两个管道
pipe(fds2);
pid_t pid = fork();
if(pid == 0){
write(fds1[1], str1, sizeof(str1));
read(fds2[0], buf, BUF_SIZE);
printf("Child process copy the message: %s\n", buf);
}else {
read(fds1[0], buf, BUF_SIZE);
printf("Parent Process copy the message: %s\n", buf);
write(fds2[1], str2, sizeof(str2));
}
return 0;
}
2.FIFO
FIFO即命名管道,在磁盘上有对应的节点,但没有数据块—换言之,只是拥有一个名字和相应的访问权限,通过mknode()
系统调用或者mkfifo()
函数来建立的。一旦建立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。
/* Create a new FIFO named PATH, with permission bits MODE. */
extern int mkfifo (const char *__path, __mode_t __mode)
__THROW __nonnull ((1));
其中的 mode 参数与open
函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK
)的区别:
- 若没有指定
O_NONBLOCK
(默认),以只读方式打开的FIFO要阻塞到其他的某个程序以写打开这个FIFO。同样以只写方式打开的FIFO要阻塞到其他某个进程以读方式打开该FIFO。 - 若指定了
O_NONBLOCK
,则以只读方式打开会立刻返回而不阻塞(不是出错返回)。而以只写方式打开,若之前没有进程以读方式打开这个FIFO则立刻出错返回。
示例代码:一个进程发送消息给另一个进程
writefifo.cpp
#include <iostream>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h> // O_WRONLY
#include <time.h> //time
#include <unistd.h>
using namespace std;
int main(){
int fd;
int n;
char buf[1024];
time_t tp;
if( mkfifo("fifo1", 0666) < 0 && errno != EEXIST) //创建FIFO管道
{
perror("Create FIFO Faileed");
}
printf("I am %d process.\n", getpt()); //说明进程的ID
if((fd = open("fifo1",O_WRONLY )) < 0){ //以只写方式打开FIFO
perror("Open FIFO failed");
exit(1);
}
for (int i = 0; i < 10; ++i) {
time(&tp); //获取当前系统时间
n = sprintf(buf, "Process %d's time is %s",getpid(), ctime(&tp));
printf("send message: %s", buf);
if(write(fd, buf, n+1) < 0)
{
perror("write FIFO Failed");
close(fd);
exit(1);
}
sleep(1);
}
close(fd);
return 0;
}
readfifo.cpp
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h> // O_WRONLY
#include <time.h> //time
#include <unistd.h>
using namespace std;
int main(){
int fd;
int len;
char buf[1024];
if((fd = open("fifo1", O_RDONLY)) < 0){ //以只读方式打开FIFO
perror("Open FIFO failed");
exit(1);
}
while ((len = read(fd, buf ,1024)) > 0) //读取FIFO管道
{
printf("Read message: %s", buf);
}
close(fd);
return 0;
}
如果在writefifo.cpp
中修改如下,设置非阻塞标志:
if((fd = open("fifo1",O_WRONLY | O_NONBLOCK)) < 0){ //以只写方式打开FIFO
如果先运行writefifo,在运行readfifo,则会出错。
3. 消息队列
消息队列,就是一个消息的链表,是一系列保存在内核的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。
特点:
- 队列独立于发送与接收进程。进程终止时,消息队列及其内容并不删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
4.共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
5. 信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6.套接字
套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。