细说linux IPC(六):pipe和FIFO

在unix系统上最早的IPC形式为管道,管道的创建使用pipe函数:

  1. #include <unistd.h>
  2. int pipe(int pipefd[2]);

该函数创建一个单向的管道,返回两个描述符 pipefd[0],和pipefd[1],pipefd[0]用于读操作,pipefd[1]用于写操作。该函数一般应用在父子进程(有亲缘关系的进 程)之间的通信,先是一个进程创建管道,再fork出一个子进程,然后父子进程可以通过管道来实现通信。
管道具有以下特点:
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

函数pipe一般使用步骤如下:
1.pipe创建管道;
2.fork创建子进程;
3.父子进程分别关闭掉读和写(或写和读)描述符;
4.读端在读描述符上开始读(或阻塞在读上等待写端完成写),写端开始写,完成父子进程通信过程。
一个简单的通信实现(来自linux man手册的修改)

  1. #include <sys/wait.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. int main(int argc, char *argv[])
  8. {
  9. int pipefd[2];
  10. pid_t cpid;
  11. char buf[128];
  12. int readlen;
  13. if (argc != 2) {
  14. fprintf(stderr, "Usage: %s <string>\n", argv[0]);
  15. return -1;
  16. }
  17. if (pipe(pipefd) < 0) {
  18. fprintf(stderr, "pipe: %s\n", strerror(errno));
  19. return -1;
  20. }
  21. cpid = fork();
  22. if (cpid < 0) {
  23. fprintf(stderr, "fork: %s\n", strerror(errno));
  24. return -1;
  25. }
  26. if (0 == cpid) { /* 子进程 */
  27. close(pipefd[1]); /* 子进程关闭写端 */
  28. readlen = read(pipefd[0], buf, 128); //子进程阻塞在读上,等待父进程写
  29. if (readlen < 0) {
  30. fprintf(stderr, "read: %s\n", strerror(errno));
  31. return -1;
  32. }
  33. write(STDOUT_FILENO, buf, readlen);
  34. write(STDOUT_FILENO, "\n", 1);
  35. close(pipefd[0]); //读完之后关闭读描述符
  36. return 0;
  37. } else { /* 父进程 */
  38. close(pipefd[0]); /*父进程关闭没用的读端 */
  39. sleep(2);
  40. write(pipefd[1], argv[1], strlen(argv[1])); //父进程开始写
  41. close(pipefd[1]); /* 父进程关闭写描述符 */
  42. wait(NULL); /* 父进程等待子进程退出,回收子进程资源 */
  43. return 0;
  44. }
  45. }

运行时将打印命令行输入参数,打印将在父进程睡眠2秒之后,子进程将阻塞在读,直到父进程写完数据,可见管道是有同步机制的,不需要自己添加同步机制。如果希望两个进程双向数据传输,那么需要建立两个管道来实现。
管道最大的劣势就是只能在拥有共同祖先进程的进程之间通信,在无亲缘关系的两个进程之间没有办法使用,不过有名管道FIFO解决了这个问题。FIFO类似 于pipe,也是只能单向传输数据,不过和pipe不同的是他可以在无亲缘关系的进程之间通信,它提供一个路径与之关联,所以只要能访问该路径的进程都可 以建立起通信,类似于前面的共享内存,都提供一个路径与之关联。

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. int mkfifo(const char *pathname, mode_t mode);

pathname 为系统路径名,mode为文件权限位,类似open函数第二个参数。
打开或创建一个新的fifo是先调用mkfifo,当指定的pathname已存在fifo时,mkfifo返回EEXIST错误,此时再调用open函数。
下面来使用mkfifo实现一个无亲缘关系进程间的双向通信,此时需要建立两个fifo,分别用于读写。服务进程循环的读并等待客户进程写,之后打印客户进程传来数据并向客户进程返回数据;客户进程向服务器写数据并等待读取服务进程返回的数据。
server process:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/stat.h>
  5. #include <sys/types.h>
  6. #include <fcntl.h>
  7. #include "slnipc.h"
  8. int main(int argc, const char *argv[])
  9. {
  10. int rc;
  11. int wr_fd, rd_fd;
  12. char sendbuf[128];
  13. char recvbuf[128];
  14. rc = mkfifo(SLN_IPC_2SER_PATH, O_CREAT | O_EXCL); //建立服务进程读的fifo
  15. if ((rc < 0 ) && (errno != EEXIST)) {
  16. fprintf(stderr, "mkfifo: %s\n", strerror(errno));
  17. return -1;
  18. }
  19. rc = mkfifo(SLN_IPC_2CLT_PATH, O_CREAT | O_EXCL); //建立服务进程写的fifo
  20. if ((rc < 0 ) && (errno != EEXIST)) {
  21. fprintf(stderr, "mkfifo: %s\n", strerror(errno));
  22. return -1;
  23. }
  24. wr_fd = open(SLN_IPC_2CLT_PATH, O_RDWR, 0);
  25. if (wr_fd < 0) {
  26. fprintf(stderr, "open: %s\n", strerror(errno));
  27. return -1;
  28. }
  29. rd_fd = open(SLN_IPC_2SER_PATH, O_RDWR, 0);
  30. if (rd_fd < 0) {
  31. fprintf(stderr, "open: %s\n", strerror(errno));
  32. return -1;
  33. }
  34. for (;;) {
  35. rc = read(rd_fd, recvbuf, sizeof(recvbuf)); //循环等待接受客户进程数据
  36. if (rc < 0) {
  37. fprintf(stderr, "read: %s\n", strerror(errno));
  38. continue;
  39. }
  40. printf("server recv: %s\n", recvbuf);
  41. snprintf(sendbuf, sizeof(sendbuf), "Hello, this is server!\n");
  42. rc = write(wr_fd, sendbuf, strlen(sendbuf));
  43. if (rc < 0) {
  44. fprintf(stderr, "write: %s\n", strerror(errno));
  45. continue;
  46. }
  47. }
  48. close(wr_fd);
  49. close(rd_fd);
  50. return 0;
  51. }

client process

  1. #include <stdio.h>
  2. #include <sys/stat.h>
  3. #include <sys/types.h>
  4. #include <fcntl.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. #include "slnipc.h"
  8. int main(int argc, const char *argv[])
  9. {
  10. int rc;
  11. int rd_fd, wr_fd;
  12. char recvbuf[128];
  13. char sendbuf[128];
  14. if (argc != 2) {
  15. fprintf(stderr, "Usage: %s <string>\n", argv[0]);
  16. return -1;
  17. }
  18. snprintf(sendbuf, sizeof(sendbuf), "%s", argv[1]);
  19. wr_fd = open(SLN_IPC_2SER_PATH, O_RDWR, 0);
  20. if (wr_fd < 0) {
  21. fprintf(stderr, "open: %s\n", strerror(errno));
  22. return -1;
  23. }
  24. rd_fd = open(SLN_IPC_2CLT_PATH, O_RDWR, 0);
  25. if (rd_fd < 0) {
  26. fprintf(stderr, "open: %s\n", strerror(errno));
  27. return -1;
  28. }
  29. rc = write(wr_fd, sendbuf, strlen(sendbuf));
  30. if (rc < 0) {
  31. fprintf(stderr, "write: %s\n", strerror(errno));
  32. return -1;
  33. }
  34. rc = read(rd_fd, recvbuf, sizeof(recvbuf));
  35. if (rc < 0) {
  36. fprintf(stderr, "write: %s\n", strerror(errno));
  37. return -1;
  38. }
  39. printf("client read: %s\n", recvbuf);
  40. close(wr_fd);
  41. close(rd_fd);
  42. return 0;
  43. }

服务器先启动运行,之后运行客户端,运行结果

  1. # ./server
  2. server recv: hi,this is fifo client
  3. # ./client "hi,this is fifo client"
  4. client read: Hello, this is server!

这里有一些类似于socket实现进程间通信过程,只是fifo的读写描述符是两个,socket的读写使用同一个描述符。fifo的出现克服了管道的只 能在有亲缘关系的进程之间的通信。和其他的进程间通信一直,fifo传送的数据也是字节流,需要自己定义协议格式来解析通信的数据,可以使用socket 章节介绍的方式来实现的通信协议。

本节源码下载:

http://download.csdn.net/detail/gentleliu/8183027

时间: 2024-10-11 22:02:02

细说linux IPC(六):pipe和FIFO的相关文章

细说linux IPC(五):system V共享内存

system V共享内存和posix共享内存类似,system V共享内存是调用shmget函数和shamat函数.           shmget函数创建共享内存区,或者访问一个存在的内存区,类似系统调用共享内存的open和posix共享内存shm_open函数.shmget函数原型为: #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); key: 函

细说linux IPC(三):mmap系统调用共享内存

前面讲到socket的进程间通信方式,这种方式在进程间传递数据时首先需要从进程1地址空间中把数据拷贝到内核,内核再将数据拷贝到进程2的地址空间 中,也就是数据传递需要经过内核传递.这样在处理较多数据时效率不是很高,而让多个进程共享一片内存区则解决了之前socket进程通信的问题.共享内存 是最快的进程间通信 ,将一片内存映射到多个进程地址空间中,那么进程间的数据传递将不在涉及内核.        共享内存并不是从某一进程拥有的内存中划分出来的:进程的内存总是私有的.共享内存是从系统的空闲内存池中

细说linux IPC(十一):各种IPC形式比较总结(完)

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 这个系列基本上到现在为止已经差不多把linux上的各种常用的IPC介绍完了,linux上面的IPC大多都是从UNIX上面继承而来. 最初Unix IPC包括:管道.FIFO.信号.System V IPC包括:System V消息队列.System V信号灯.System V共享内存区.由于Unix版本的多样性,电子电气工程协会(IEEE)开发

Linux IPC之管道和FIFO

导言:管道是UNIX系统上最古老的IPC方法,管道提供了一种优雅的解决方案:给定两个运行不同程序的进程,在shell中如何让一个进程的输出作为另一个进程的输入?管道可以用来在相关(一个共同的祖先进程创建管道)进程之间传递数据.FIFO是管道概念的一个变体,它们之间的一个重要差别在于FIFO可以用于任意进程间的通信. 概述 每个shell用户都对在命令中使用管道比较熟悉,例如,统计一个目录中文件的数目: ls | wc -l 解释:为了执行上面的命令,shell创建了两个进程来分别执行ls和wc(

细说linux IPC(十):system V 消息队列

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] system V消息队列和posix消息队列类似,linux系统这两种消息队列都支持.先来看一下system V消息队列相关操作及其函数. msgget()函数创建一个消息队列或打开一个消息队列. #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h&

Linux IPC实践(3) --具名FIFO

FIFO具名/命名管道 (匿名)管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信. 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道;命名管道是一种特殊类型的文件. 创建一个命名管道 1)命名管道可以从命令行上创建: $ mkfifo <filename> 2)命名管道在程序里创建: #include <sys/types.h> #include <sys/stat.h> int mkfifo(const

细说linux IPC(八):信号(下)

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 上一节的说了使用kill函数来发送信号和使用signal函数来安装信号处理函数,这一节我们使用另外一种方式来实现安装信号处理和发送信号. 早期UNIX只支持SIGRTMIN之前的不可靠信号,后来增加了SIGRTMIN到SIGRTMAX的可靠信号,同时也增加了信号发送和安装的方式,使用sigqueue()函数可以发送信号,使用sigaction函

细说linux IPC(四):posix 共享内存

上一节讲了由open函数打开一个内存映射文件,再由mmap函数把得到的描述符映射到当前进程地址空间中来.这一节说说另外一种类似的共享内存方法,即 有shm_open函数打开一个Posix.1 IPC名字(也许是文件系统中的一个路径名),所返回的描述符由函数mmap映射到当前进程地址空间.        posix共享内存和尚上一节类似,首先需要制定一个名字参数调用shm_open ,以创建一个新的共享内存对象或打开一个已存在的共享内存对象:然后再调用mmap把共享内存区映射到调用进程的地址空间中

细说linux IPC(七):信号(上)

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 信号类似于中断请求,一个进程不会阻塞在某处等待信号的到来,也不会知道信号何时能到来,信号的产生是随机的,进程只需要注册信号处理函数,在信号到来时执行信号处理函数即可. linux系统支持的信号可以通过命令kill -l来查看: # kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTR