Linux进程间通信——管道

简介:

Linux进程间通信机制:

1.同一主机进程间通信机制:

  Unix方式:有名管道FIFO、无名管道PIPE、信号Signal

  SystemV方式:信号量、消息队列、共享内存

2.网络通信:RPC(Remote Procedure Call)、Socket

管道

管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者可用于具有亲缘关系进程间的通信,即可用于父进程和子进程间的通信,后者额克服了管道没有名字的限制,因此,除具有前者所具有的功能外,它还允许无亲缘关系进程间的通信,即可用于运行于同一台机器上的任意两个进程间的通信。 

无名管道由pipe()函数创建:

#i nclude <unistd.h>
        int pipe(int filedis[2]);

参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。 无名管道占用两个文件描述符,  不能被非血缘关系的进程所共享,  一般应用在父子进程中. 

特点:

    半双工单向、父子进程间、一端写一段读、没有名字、缓冲区有限、数据无格式

    无名管道常用于父子进程中,  可简单分为单向管道流模型和双向管道流模型.  其中,  单向管道流根据流向分为从父进程流向子进程的管道和从子进程流向父进程的管道. 

    下面设计一个实例,  数据从父进程流向子进程:父进程向管道写入一行字符,  子进程读取数据并打印到

屏幕上. 

[bill@billstone Unix_study]$ cat pipe1.c 

#include <unistd.h> 

#include <stdio.h> 

#include <sys/types.h> 

#include <assert.h> 

int main() 

                int fildes[2]; 

                pid_t pid; 

                int i,j; 

                char buf[256]; 

                assert(pipe(fildes) == 0);                          //  创建管道 

                assert((pid = fork()) >= 0);                      //  创建子进程 

                if(pid == 0){                                        //  子进程 

                                close(fildes[1]);                                //  子进程关闭管道输出 

                                memset(buf, 0, sizeof(buf)); 

                                j = read(fildes[0], buf, sizeof(buf)); 

                                fprintf(stderr, "[child] buf=[%s] len[%d]\n", buf, j); 

                                return; 

                } 

                close(fildes[0]);                                              //  父进程关闭管道输入 

                write(fildes[1], "hello!", strlen("hello!")); 

                write(fildes[1], "world!", strlen("world!")); 

                return 0; 

[bill@billstone Unix_study]$ make pipe1 

cc          pipe1.c      -o pipe1 

[bill@billstone Unix_study]$ ./pipe1 

[child] buf=[hello!world!] len[12]                              //  子进程一次就可以读出两次父进程写入的数据 

[bill@billstone Unix_study]$ 

    从上面可以看出,  在父进程中关闭 fildes[0],  向 fildes[1]写入数据;  在子进程中关闭 filedes[1],  从

fildes[0]中读取数据可实现从父进程流向子进程的管道. 

    在进程的通信中,  我们无法判断每次通信中报文的字节数,  即无法对数据流进行  自行拆分,  侧耳发生了上例中子进程一次性读取父进程两次通信的报文情况. 

管道是进程之间的一种单向交流方法,  要实现进程间的双向交流,  就必须通过两个管道来完成.  双向管道流的创立过程如下: 

    (1)  创建管道,  返回两个无名管道文件描述符 fildes1 和 fildes2: 

    (2)  创建子进程,  子进程中继承管道 fildes1 和 fildes2. 

    (3)  父进程关闭只读文件描述符 fildes1[0],  只写描述符 fildes2[1] 

    (4)  子进程关闭只写文件描述符 fildes1[1],  只读描述符 fildes2[0] 

    创建的结果如下: 

              父进程  --写--> fildes1[1] --管道--> fildes1[0] --读-->  子进程 

              父进程  <--读-- fildes2[0] <--管道-- fildes2[1] <--写--  子进程 

    这里实现一个父子进程间双向通信的实例:  父进程先向子进程发送两次数据,  再接收子进程传送刚来

的两次数据.

  为了正确拆分时间留从父进程流向子进程的管道采用'固定长度'方法传送数据;  从子进程流向

父进程的管道采用'显式长度'方法传回数据. 

    (1)  固定长度方式 

char bufG[255]; 

void WriteG(int fd, char *str, int len){ 

                memset(bufG, 0, sizeof(bufG)); 

                sprintf(bufG, "%s", str); 

                write(fd, bufG, len); 

char *ReadG(int fd, int len){ 

                memset(bufG, 0, sizeof(bufG)); 

                read(fd, bufG, len); 

                return(bufG); 

    在此设计中,  父子程序需要约定好每次发送数据的长度;  且长度不能超过 255 个字符.

    (2)  显式长度方式 

char bufC[255]; 

void WriteC(int fd, char str[]){ 

                sprintf(bufC, "%04d%s", strlen(str), str); 

                write(fd, bufC, strlen(bufC)); 

char *ReadC(int fd){ 

                int i, j; 

                memset(bufC, 0, sizeof(bufC)); 

                j = read(fd, bufC, 4); 

                i = atoi(bufC); 

                j = read(fd, bufC, i); 

                return(bufC); 

    父子进程约定在发送消息前先指明消息的长度. 

    (3)  主程序 

#include <unistd.h> 

#include <stdio.h> 

#include <assert.h> 

#include <sys/types.h> 

int main() 

                int fildes1[2], fildes2[2]; 

                pid_t pid; 

                char buf[255]; 

                assert(pipe(fildes1) == 0); 

                assert(pipe(fildes2) == 0); 

                assert((pid = fork()) >= 0); 

                if(pid == 0){ 

                                close(fildes1[1]); 

                                close(fildes2[0]); 

                                strcpy(buf, ReadG(fildes1[0], 10)); 

                                fprintf(stderr, "[child] buf = [%s]\n", buf); 

                                WriteC(fildes2[1], buf); 

                                strcpy(buf, ReadG(fildes1[0], 10)); 

                                fprintf(stderr, "[child] buf = [%s]\n", buf); 

                                WriteC(fildes2[1], buf); 

                                return(0); 

                } 

                close(fildes1[0]); 

                close(fildes2[1]); 

                WriteG(fildes1[1], "hello!", 10); 

                WriteG(fildes1[1], "world!", 10); 

                fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0])); 

                fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0])); 

                return 0; 

    执行结果如下: 

[bill@billstone Unix_study]$ make pipe2 

cc          pipe2.c      -o pipe2 

[bill@billstone Unix_study]$ ./pipe2 

[child] buf = [hello!] 

[child] buf = [world!] 

[father] buf = [hello!] 

[father] buf = [world!] 

[bill@billstone Unix_study]$   

  dup dup2复制文件描述符

   

在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。

下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:

方式一:mkfifo("myfifo","rw");

方式二:mknod myfifo p

生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。

    管道是 UNIX 中最古老的进程间通信工具,  它提供了进程之间的一种单向通信的方法. 

  popen 模型 

    从前面的程序可以看出,  创建连接标准 I/O 的管道需要多个步骤,  这需要使用大量的代码, UNIX 为了

简化这个操作,  它提供了一组函数实现之.  原型如下: 

#include <stdio.h> 

FILE *popen(const char *command, char *type); 

int pclose(FILE *stream); 

    函数 popen 调用成功时返回一个标准的 I/O 的 FILE 文件流,  其读写属性由参数 type 决定. 

    这里看一个模拟 shell 命令'ps -ef | grep init'的实例. 

[bill@billstone Unix_study]$ cat pipe3.c 

#include <stdio.h> 

#include <assert.h> 

int main() 

                FILE *out, *in; 

                char buf[255]; 

                assert((out = popen("grep init", "w")) != NULL);                //    创建写管道流 

                assert((in = popen("ps -ef", "r")) != NULL);                      //  创建读管道流 

                while(fgets(buf, sizeof(buf), in))                //  读取 ps -ef 的结果 

                                fputs(buf, out);                            //  转发到 grep init 

                pclose(out); 

                pclose(in); 

                return 0; 

[bill@billstone Unix_study]$ make pipe3 

cc          pipe3.c      -o pipe3 

[bill@billstone Unix_study]$ ./pipe3 

root                  1          0    0 Apr15 ?                00:00:04 init 

bill            1392    1353    0 Apr15 ?                00:00:00 /usr/bin/ssh-agent /etc/X11/xinit/Xclients 

bill          14204 14203    0 21:33 pts/0        00:00:00 grep init 

[bill@billstone Unix_study]$ ps -ef | grep init 

root                  1          0    0 Apr15 ?                00:00:04 init 

bill            1392    1353    0 Apr15 ?                00:00:00 /usr/bin/ssh-agent /etc/X11/xinit/Xclients 

bill          14207    1441    0 21:35 pts/0        00:00:00 grep init 

[bill@billstone Unix_study]$ 

    读者可以从上面自行比较同 Shell 命令'ps -ef | grep init'的执行结果. 

有名管道 FIFO 

    FIFO 可以在整个系统中使用. 

    在 Shell 中可以使用 mknod 或者 mkfifo 命令创建管道;  而在 C 程序中,  可以使用 mkfifo 函数创建有名管道. 

    要使用有名管道,  需要下面几个步骤: 

    (1)  创建管道文件 

    (2)  在某个进程中以只写方式打开管道文件,  并写管道 

    (3)  在某个进程中以只读方式打开管道文件,  并读管道 

    (4)  关闭管道文件. 

    低级文件编程库和标准文件编程库都可以操作管道.  管道在执行读写操作之前,  两端必须同时打开,  否

则执行打开管道某端操作的进程将一直阻塞到某个进程以相反方向打开管道为止. 

C代码 

  1. /*fifoserver.c:向FIFO中写入信息*/
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #define FIFO_SERVER "FIFO4"
  7. main(int argc,char** argv)  
  8. {  
  9. int fd=0;  
  10. char w_buf[4096];  
  11. int real_wnum;  
  12.     memset(w_buf,0,4096);  
  13. if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL|0666)<0)&&(errno!=EEXIST))  
  14.         printf("cannot create fifoserver\n");  
  15. /*此处存在着打开依赖,即若没有读端打开FIFO的话,写端就阻塞在写端*/
  16.     fd=open(FIFO_SERVER,O_WRONLY);  
  17. if(fd==-1)  
  18.         printf("open error; no reading process\n");  
  19.     printf("%d\n",fd);  
  20.     real_wnum=write(fd,w_buf,2048);  
  21. if(real_wnum==-1)  
  22.         printf("write to fifo error; try later\n");  
  23. else
  24.         printf("real write num is %d\n",real_wnum);  
  25. /*往FIFO写入的数据都是原子的,如果没有足够的空间,则会等待,而不是一点一点的写入。*/
  26.     real_wnum=write(fd,w_buf,4096);  
  27. if(real_wnum==-1)  
  28.         printf("write to fifo error; try later\n");  
  29. else
  30.         printf("real write num is %d\n",real_wnum);  

C代码 

  1. /*fifoclient.c:从FIFO中读出数据*/
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #define FIFO_SERVER "FIFO4"
  7. main(int argc,char** argv)  
  8. {  
  9. char r_buf[4096];  
  10. int  fd;  
  11. int  r_size;  
  12. int  ret_size;  
  13.     r_size=atoi(argv[1]);  
  14.     memset(r_buf,0,sizeof(r_buf));  
  15.     fd=open(FIFO_SERVER,O_RDONLY);  
  16. if(fd==-1)  
  17.     {  
  18.         printf("open %s for read error\n");  
  19.         exit(1);  
  20.     }  
  21.     printf("%d\n",fd);  
  22. while(1)  
  23.     {  
  24.         ret_size=read(fd,r_buf,r_size);  
  25. if(ret_size==-1)  
  26.             printf("no data avlaible\n");  
  27. else
  28.             printf("real read bytes %d\n",ret_size);  
  29.         sleep(1);  
  30.     }     
  31.     unlink(FIFO_SERVER);  

    下面是一个简单的实例. 

    首先是写进程:  创建 FIFO 文件,  再打开写端口,  然后读取标准输入并将输入信息发送到管道中,  当键

盘输入'exit'或'quit'时程序退出. 

[bill@billstone Unix_study]$ cat fifo1.c 

#include <stdio.h> 

#include <assert.h> 

#include <sys/types.h> 

#include <sys/stat.h> 

#include <sys/errno.h> 

extern int errno; 

int main() 

                FILE *fp; 

                char buf[255]; 

                assert((mkfifo("myfifo", S_IFIFO|0666) > 0) || (errno == EEXIST)); 

                while(1){ 

                                assert((fp = fopen("myfifo", "w")) != NULL); 

                                printf("please input: "); 

                                fgets(buf, sizeof(buf), stdin); 

                                fputs(buf, fp); 

                                fclose(fp); 

                                if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0) 

                                                break; 

                } 

                return 0; 

[bill@billstone Unix_study]$ make fifo1 

cc          fifo1.c      -o fifo1 

[bill@billstone Unix_study]$   

    然后是读进程:  打开管道的读端口,  从管道中读取信息(以行为单位),  并将此信息打印到屏幕上.  当读

取到'exit'或者'quit'时程序退出. 

[bill@billstone Unix_study]$ cat fifo2.c 

#include <stdio.h> 

#include <assert.h> 

#include <sys/types.h> 

#include <sys/stat.h> 

int main() 

                FILE *fp; 

                char buf[255]; 

                while(1){ 

                                assert((fp = fopen("myfifo", "r")) != NULL); 

                                fgets(buf, strlen(buf), fp); 

                                printf("gets: [%s]", buf); 

                                fclose(fp); 

                                if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0) 

                                                break; 

                } 

                return 0; 

[bill@billstone Unix_study]$ make fifo2 

cc          fifo2.c      -o fifo2 

[bill@billstone Unix_study]$   

    在一个终端上执行 fifo1,  而在另一个终端上执行 fifo2. 

    我们先输入'hello', 'world',  然后再输入'exit'退出: 

[bill@billstone Unix_study]$ ./fifo1 

please input: hello 

please input: world 

please input: exit 

[bill@billstone Unix_study]$   

    我们可以看到读出结果如下: 

[bill@billstone Unix_study]$ ./fifo2 

gets: [hello 

]gets: [world]gets: [exit][bill@billstone Unix_study]$ 

    看到上面的输出结果,  您可能认为是我写错了.  其实不是的,  读出结果正是如此,  其实按照我们的本意,

正确的输出结果应该是这样的: 

[bill@billstone Unix_study]$ ./fifo2 

gets: [hello 

]gets: [world 

]gets: [exit


本文转自feisky博客园博客,原文链接:http://www.cnblogs.com/feisky/archive/2010/03/24/1693484.html,如需转载请自行联系原作者


相关文章
|
2月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
143 2
|
2月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
47 2
|
6天前
|
Linux Shell
6-9|linux查询现在运行的进程
6-9|linux查询现在运行的进程
|
29天前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
6天前
|
SQL 网络协议 数据库连接
已解决:连接SqlServer出现 provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程【C#连接SqlServer踩坑记录】
本文介绍了解决连接SqlServer时出现“provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程”错误的步骤,包括更改服务器验证模式、修改sa用户设置、启用TCP/IP协议,以及检查数据库连接语句中的实例名是否正确。此外,还解释了实例名mssqlserver和sqlserver之间的区别,包括它们在默认设置、功能和用途上的差异。
|
2月前
|
消息中间件 Linux
Linux进程间通信
Linux进程间通信
35 1
|
19天前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
22 0
|
1月前
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
|
2月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
52 0
|
Linux Shell
详解linux进程间通信-管道 popen函数 dup2函数
  前言:进程之间交换信息的唯一方法是经由f o r k或e x e c传送打开文件,或通过文件系统。本章将说明进程之间相互通信的其他技术—I P C(InterProcess Communication)。
1566 0
下一篇
无影云桌面