进程间通信的方式

225 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1.管道

管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

1.匿名管道:

概念:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,一般使用fork函数实现父子进程的通信。

2.命名管道:

FIFO,也称为命名管道,它是一种文件类型。

概念:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,没有血缘关系的进程也可以进程间通信。

3.特点:

1.面向字节流,

2.生命周期随内核

3.自带同步互斥机制。

4.半双工,单向通信,两个管道实现双向通信。

4.匿名管道原型

1 #include <unistd.h>2 int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:

image.png

要关闭管道只需将这两个文件描述符关闭即可。

5.匿名管道例子

单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:

image.png

若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

#include<stdio.h>#include<unistd.h>int main() {     int fd[2];  // 两个文件描述符     pid_t pid;     char buff[20];      if(pipe(fd) < 0)  // 创建管道         printf("Create Pipe Error!\n");      if((pid = fork()) < 0)  // 创建子进程         printf("Fork Error!\n");     else if(pid > 0)  // 父进程     {         close(fd[0]); // 关闭读端         write(fd[1], "hello world\n", 12);     }     else     {         close(fd[1]); // 关闭写端         read(fd[0], buff, 20);         printf("%s", buff);     }      return 0; }

6.命名管道原型

#include <sys/stat.h>// 返回值:成功返回0,出错返回-1int mkfifo(const char *pathname, mode_t mode);

其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。

当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。

  • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

7.命名管道原型

FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。下面的例子演示了使用 FIFO 进行 IPC 的过程:

write_fifo.c

#include<stdio.h> #include<stdlib.h> // exit #include<fcntl.h> // O_WRONLY #include<sys/stat.h> #include<time.h> // time int main() {         int fd;         int n, i;         char buf[1024];         time_t tp;         printf("I am %d process.\n", getpid()); // 说明进程ID         if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO         {                 perror("Open FIFO Failed");                 exit(1);         }         for(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) // 写入到FIFO中                 {                         perror("Write FIFO Failed");                         close(fd);                         exit(1);                 }                 sleep(1); // 休眠1秒         }         close(fd); // 关闭FIFO文件         return 0; }

read_fifo.c

#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<fcntl.h> #include<sys/stat.h> int main() {         int fd;         int len;         char buf[1024];         if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 创建FIFO管道                 perror("Create FIFO Failed");         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); // 关闭FIFO文件         return 0; }

在两个终端里用 gcc 分别编译运行上面两个文件,可以看到输出结果如下:

[root@VM_0_15_centos fifo]# ./write I am 9102 process.Send message: Process 9102's time is Fri Apr 17 10:12:00 2013Send message: Process 9102's time is Fri Apr 17 10:12:01 2013Send message: Process 9102's time is Fri Apr 17 10:12:02 2013Send message: Process 9102's time is Fri Apr 17 10:12:03 2013Send message: Process 9102's time is Fri Apr 17 10:12:04 2013Send message: Process 9102's time is Fri Apr 17 10:12:05 2013Send message: Process 9102's time is Fri Apr 17 10:12:06 2013Send message: Process 9102's time is Fri Apr 17 10:12:07 2013Send message: Process 9102's time is Fri Apr 17 10:12:08 2013Send message: Process 9102's time is Fri Apr 17 10:12:09 2013

[root@VM_0_15_centos fifo]# ./read Read message: Process 9102's time is Fri Apr 17 10:12:00 2020

上述例子可以扩展成 客户进程—服务器进程 通信的实例,write_fifo的作用类似于客户端,可以打开多个客户端向一个服务器发送请求信息,read_fifo类似于服务器,它适时监控着FIFO的读端,当有数据时,读出并进行处理,但是有一个关键的问题是,每一个客户端必须预先知道服务器提供的FIFO接口,下图显示了这种安排:

2.消息队列

1.概念:在内核中创建一队列,队列中每个元素是一个数据报,不同的进程可以通过句柄去访问这个队列。消息队列提供了⼀个从⼀个进程向另外⼀个进程发送⼀块数据的⽅法。 每个数据块都被认为是有⼀个类型,接收者进程接收的数据块可以有不同的类型值 .消息队列也有管道⼀样的不⾜,就是每个消息的最⼤⻓度是有上限的(MSGMAX),每个消息队 列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有⼀个上限(MSGMNI)

2.特点:

1. 消息队列可以认为是一个全局的一个链表,链表节点钟存放着数据报的类型和内容,有消息队列的标识符进行标记。

2.消息队列允许一个或多个进程写入或者读取消息。

3.消息队列的生命周期随内核。

4.消息队列可实现双向通信。

3.信号量

1.概念

在内核中创建一个信号量集合(本质是个数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1,

(1) P(sv):如果sv的值⼤大于零,就给它减1;如果它的值为零,就挂起该进程的执⾏ 。

(2) V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运⾏,如果没有进程因等待sv⽽挂起,就给它加1。

PV操作用于同一进程,实现互斥。

PV操作用于不同进程,实现同步。

2.功能:

对临界资源进行保护。

4.共享内存

1.概念:

将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。

2.特点:

1.不用从用户态到内核态的频繁切换和拷贝数据,直接从内存中读取就可以。

2.共享内存是临界资源,所以需要操作时必须要保证原子性。使用信号量或者互斥锁都可以。

3.生命周期随内核。

5.总结

所有的以上的方式都是生命周期随内核,不手动释就不会消失。

1.管道:速度慢,容量有限,只有父子进程能通讯

2.FIFO:任何进程间都能通讯,但速度慢

3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

4.信号量:不能传递复杂消息,只能用来同步

5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

参考文献:www.phpmianshi.com/?id=55