linux进程间通信快速入门【一】:管道编程

介绍

	管道本质上就是一个文件,前面的进程以写方式打开文件,后面的进程以读方式打开。这样前面写完后面读,于是就实现了通信。虽然实现形态上是文件,但是管道本身并不占用磁盘或者其他外部存储的空间。在Linux的实现上,它占用的是内存空间。所以,Linux上的管道就是一个操作方式为文件的内存缓冲区。管道分类:匿名管道、命名管道

命令行中使用

1、mkfifo或mknod命令来创建一个命名管道

[root@VM-90-225-centos ~]# mkfifo pipe
[root@VM-90-225-centos ~]# ls -l pipe
prw-r--r-- 1 root root 0 Feb 28 21:02 pipe

我们现在让一个进程写这个管道文件:

 echo 12345 > pipe

此时这个写操作会阻塞,因为管道另一端没有人读。此时如果有进程读这个管道,那么这个写操作的阻塞才会解除:

[root@VM-90-225-centos ~]# cat pipe 
12345

当我们cat完这个文件之后,另一端的echo命令也返回了.
Linux系统无论对于命名管道和匿名管道,底层都用的是同一种文件系统的操作行为,这种文件系统叫pipefs,可以通过下面命令查看是否具有这个系统:

[root@VM-90-225-centos ~]# cat /proc/filesystems |grep pipefs
nodev   pipefs
nodev   rpc_pipefs

系统编程中使用

匿名管道和命名管道分别叫做PIPE和FIFO,创建匿名管道的系统调用是pipe(),而创建命名管道的函数是mkfifo()。
匿名管道:

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

这个方法将会创建出两个文件描述符:
pipefd[0]是读方式打开,作为管道的读描述符。pipefd[1]是写方式打开,作为管道的写描述符。从管道写端写入的数据会被内核缓存直到有人从另一端读取为止。

pipe示例一:简单的写入+读出

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>#define STRING "hello world!"int main()
{int pipefd[2];char buf[BUFSIZ];// 创建一组管道if (pipe(pipefd) == -1) {perror("pipe()");exit(1);}// 从[1]写入STRINGif (write(pipefd[1], STRING, strlen(STRING)) < 0) {perror("write()");exit(1);}// 从[0]读出,结果存到bufif (read(pipefd[0], buf, BUFSIZ) < 0) {perror("write()");exit(1);}// 打印读出来的结果printf("%s\n", buf);exit(0);
}

程序创建了一个管道,并且对管道写了一个字符串之后从管道读取,并打印在标准输出上。
当然这不属于进程间通信,实际情况中我们不会在单个进程中使用管道。
进程在pipe创建完管道之后,往往都要fork产生子进程。下面的demo是父子两个进程使用一个管道可以完成半双工通信。此时,父进程可以通过fd[1]给子进程发消息,子进程通过fd[0]读。子进程也可以通过fd[1]给父进程发消息,父进程用fd[0]读。

pipe示例二:父子进程半双工通信

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define STRING "hello world!"int main()
{int pipefd[2];pid_t pid;char buf[BUFSIZ];// 创建管道if (pipe(pipefd) == -1) {perror("pipe()");exit(1);}// fork产生子进程pid = fork();if (pid == -1) {perror("fork()");exit(1);}// fork()新进程返回0,旧进程返回新进程的进程ID。if (pid == 0) {/* this is child. */printf("Child pid is: %d\n", getpid());// 子进程会继承父进程对应的文件描述符// 父进程先pipe创建管道之后,子进程也会得到同一个管道的读写文件描述符if (read(pipefd[0], buf, BUFSIZ) < 0) {perror("write()");exit(1);}printf("%s\n", buf);// 清空bufbzero(buf, BUFSIZ);snprintf(buf, BUFSIZ, "Message from child: My pid is: %d", getpid());// 把读取到的数据重新发送给主进程if (write(pipefd[1], buf, strlen(buf)) < 0) {perror("write()");exit(1);}} else {/* this is parent */printf("Parent pid is: %d\n", getpid());snprintf(buf, BUFSIZ, "Message from parent: My pid is: %d", getpid());// 父进程写数据到管道if (write(pipefd[1], buf, strlen(buf)) < 0) {perror("write()");exit(1);}// 等待1ssleep(1);// 清空bufbzero(buf, BUFSIZ);// 读取管道消息到bufif (read(pipefd[0], buf, BUFSIZ) < 0) {perror("write()");exit(1);}printf("%s\n", buf);wait(NULL);}exit(0);
}

打印结果:

Parent pid is: 17697
Child pid is: 17702
Message from parent: My pid is: 17697
Message from child: My pid is: 17702

如果在vscode中debug的话是debug不到if (pid == 0) 的分支的,你只能debug主进程的流程。
从以上的demo中用同一个管道的父子进程可以分时给对方发送消息,我们可以看到对管道读写的一些特点:
1、在管道中没有数据的情况下,对管道的读操作会阻塞,直到管道内有数据为止。
2、当一次写的数据量不超过管道容量的时候,对管道的写操作一般不会阻塞,直接将要写的数据写入管道缓冲区即可。
管道实际上就是内核控制的一个内存缓冲区,既然是缓冲区,就有容量上限。我们把管道一次最多可以缓存的数据量大小叫做PIPESIZE。内核在处理管道数据的时候,底层也要调用类似read和write这样的方法进行数据拷贝,这种内核操作每次可以操作的数据量也是有限的,一般的操作长度为一个page,即默认为4k字节。我们把每次可以操作的数据量长度叫做PIPEBUF。POSIX标准中,对PIPEBUF有长度限制,要求其最小长度不得低于512字节。PIPEBUF的作用是,内核在处理管道的时候,如果每次读写操作的数据长度不大于PIPEBUF时,保证其操作是原子的。而PIPESIZE的影响是,大于其长度的写操作会被阻塞,直到当前管道中的数据被读取为止。
在Linux 2.6.11之前,PIPESIZE和PIPEBUF实际上是一样的。在这之后,Linux重新实现了一个管道缓存,并将它与写操作的PIPEBUF实现成了不同的概念,形成了一个默认长度为65536字节的PIPESIZE,而PIPEBUF只影响相关读写操作的原子性。从Linux 2.6.35之后,在fcntl系统调用方法中实现了F_GETPIPE_SZ和F_SETPIPE_SZ操作,来分别查看当前管道容量和设置管道容量。管道容量容量上限可以在/proc/sys/fs/pipe-max-size进行设置。
在实际情境下,半双工管道管道的两端都可能有多个进程进行读写处理。如果再加上线程,则事情可能变得更复杂。实际上,我们在使用管道的时候,并不推荐这样来用。管道推荐的使用方法是其单工模式:即只有两个进程通信,一个进程只写管道,另一个进程只读管道。

pipe示例三:父子进程单工通信

这个程序实际上比上一个要简单,父进程关闭管道的读端,只写管道。子进程关闭管道的写端,只读管道。
此时两个进程就只用管道实现了一个单工通信,并且这种状态下不用考虑多个进程同时对管道写产生的数据交叉的问题,这是最经典的管道打开方式,也是我们推荐的管道使用方式。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define STRING "hello world!"int main()
{int pipefd[2];pid_t pid;char buf[BUFSIZ];if (pipe(pipefd) == -1) {perror("pipe()");exit(1);}pid = fork();if (pid == -1) {perror("fork()");exit(1);}if (pid == 0) {/* this is child. */close(pipefd[1]);printf("Child pid is: %d\n", getpid());// 读if (read(pipefd[0], buf, BUFSIZ) < 0) {perror("write()");exit(1);}printf("%s\n", buf);} else {/* this is parent */close(pipefd[0]);printf("Parent pid is: %d\n", getpid());snprintf(buf, BUFSIZ, "Message from parent: My pid is: %d", getpid());// 写if (write(pipefd[1], buf, strlen(buf)) < 0) {perror("write()");exit(1);}wait(NULL);}exit(0);
}

命名管道
命名管道在底层的实现跟匿名管道完全一致,区别只是命名管道会有一个全局可见的文件名以供别人open打开使用。再程序中创建一个命名管道文件的方法有两种,一种是使用mkfifo函数。另一种是使用mknod系统调用

fifo示例:

client端

/* 这是一个命名管道的实现demo,实现两个进程间聊天功能* */
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>int main()
{char *file = "./test.fifo";umask(0); //设置umask,仅在当前进程有效。if(mkfifo(file,0663)<0){if(errno == EEXIST){printf("fifo exist\n");}else {perror("mkfifo\n");return -1;}}int fd = open(file,O_WRONLY);if(fd<0){perror("open error");return -1;}printf("open fifo success!!!\n");while(1){printf("input: ");fflush(stdout);char buff[1024]={0};scanf("%s",buff);write(fd,buff,strlen(buff));}return 0;
}

server端:

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>int main()
{char *file = "./test.fifo";umask(0); //设置umask,仅在当前进程有效。if(mkfifo(file,0663)<0){if(errno == EEXIST){printf("fifo exist\n");}else {perror("mkfifo\n");return -1;}}int fd = open(file,O_RDONLY);if(fd<0){perror("open error");return -1;}printf("open fifo success!!!\n");while(1){char buff[1024] = {0};int ret = read(fd,buff,1024);if(ret>0){printf("peer say:%s\n",buff);}}return 0;
}

然后在子目录下编译:

g++ ./server.cpp -o server
g++ ./client.cpp -o client

然后在两个终端页面上分别运行:

./server
./client

即可进行单向通信,一般工程中两个进程建立两个fifo就可以进行双向通信了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/376652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

返回长度hdu 1518 square

查了好多资料&#xff0c;发现还是不全&#xff0c;干脆自己整理吧&#xff0c;至少保证在我的做法正确的&#xff0c;以免误导读者&#xff0c;也是给自己做个记载吧&#xff01; 题目的意思是比较明显的&#xff0c;就是当初给你m根木棒&#xff0c;当初让你判断利用这些木棒…

POJ 3233 Matrix Power Series 矩阵快速幂 + 二分

题意&#xff1a;求矩阵的次方和 解题思路&#xff1a;最容易想到方法就是两次二分因为 我们可以把一段 A^1 A^2 .......A^K 变成 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) 当k 为奇数的时候 或者 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) A^K 当K 为偶数的时候…

时间序列进行分析的一些手法以及代码实现(移动平均、指数平滑、SARIMA模型、时间序列的(非)线性模型)

文章目录1、移动平均moving average方法weighted average方法2、指数平滑单指数平滑 exponential_smoothing双指数平滑三指数平滑 Triple exponential smoothing3、平稳性以及时间序列建模SARIMA模型4、时间序列的&#xff08;非&#xff09;线性模型时间序列的滞后值使用线性回…

政权组织形式

神马国家结构、政权组织形式的 现在我比较明确的是英国的政权组织形式是式君主立宪制、美国是总统制、中国是人民代表大会制。 目前,世界各国采用的国家结构可分为单一制和复合制两大类。其中&#xff0c;复合制国家结构形式主要包括联邦制和邦联制两种类型。英国、法国、意大利…

三大平衡树(Treap + Splay + SBT)总结+模板

Treap树 核心是 利用随机数的二叉排序树的各种操作复杂度平均为O(lgn) Treap模板&#xff1a; #include <cstdio> #include <cstring> #include <ctime> #include <iostream> #include <algorithm> #include <cstdlib> #include <cmath…

mysqld进程 ut_delay 占用率过高

采用性能分析工具perf top -p mysqld进程 在测试mysql数据库时&#xff0c;用perf top如果看到热点函数是ut_delay或者_raw_spin_lock的话&#xff0c;说明锁争用比较严重。 ut_delay这是innodb的一个自旋琐。也就是说&#xff0c;在这里由于锁等待&#xff0c;innodb不停地在…

TClientDataSet使用要点

TClientDataSet控件继承自TDataSet&#xff0c;其数据存储文件格式扩展名为 .cds&#xff0c;是基于文件型数据存储和操作的控件。该控件封装了对数据进行操作处理的接口和功能&#xff0c;而本身并不依赖上述几种数据库驱动程序&#xff0c;基本上能满足单机"瘦"数据…

滑动窗口在重构数据集的作用

step1&#xff1a;使用滑动窗口重构数据集 给定时间序列数据集的数字序列&#xff0c;我们可以将数据重构为看起来像监督学习问题。 我们可以通过使用以前的时间步作为输入变量并使用下一个时间步作为输出变量来做到这一点。 通过观察重构后的数据集与原本的时间序列&…

sliverlight - Unhandled Error in Silverlight Application错误

使用firebug控制台输出错误&#xff1a; Unhandled Error in Silverlight Application 查询“GetFlow_Process”的 Load 操作失败。远程服务器返回了错误: NotFound。 位于 System.ServiceModel.DomainServices.Client.OperationBase.Complete(Exception error) 位于 System.S…

前向验证对于模型的更新作用

首先&#xff0c;让我们看一个小的单变量时间序列数据&#xff0c;我们将用作上下文来理解这三种回测方法&#xff1a;太阳黑子数据集。该数据集描述了刚刚超过 230 年&#xff08;1749-1983 年&#xff09;观察到的太阳黑子数量的每月计数。 数据集显示了季节之间差异很大的…

2014年9月21日_随笔,jdic,ETL,groovy,Nutz好多东西想学

&#xff08;1&#xff09;老妈十一要回老家&#xff0c;才突然发现买票好难啊。有亲朋很重要 &#xff08;2&#xff09;这周我做了什么。jdic,ETL,groovy, Nutz好多东西想学。 Nutz开发成员专访、Nutz优酷视频(演讲)、Nutz 入门教程、 &#xff08;3&#xff09;想改变&#…

PHP-面向对象(八)

1、多态的介绍与优势 多态性是继抽象和继承后&#xff0c;面向对象语言的第三个特征。从字面上理解&#xff0c;多态的意思是“多种形态”&#xff0c;简单来说&#xff0c;多态是具有表现多种形态的能力的特征&#xff0c;在OO中是指“语言具有根据对象的类型以不同方式处理。…

双指数平滑中参数对于预测模型的影响

先看看α 在β一致的情况下&#xff0c;α越小&#xff0c;模型越滞后。 再看看β 在α一致的情况下&#xff0c;β越大&#xff0c;模型对于趋势的预测更敏锐。

SQL 性能不佳的几个原因

SQL 性能不佳的几个原因 •不准确的统计数据•差劲的索引•差劲的查询设计 •差劲的执行计划&#xff0c;通常是由不正确的参数引起的•过度阻塞和死锁 •非基于集合的操作•不良数据库设计 •过度碎片 •不能重复使用执行计划 •查询频繁重编译 •不当使用游标 •数据库日志的…

分页查询

分页查询算是比较常用的一个查询了在DAO层主要是查两个数据第一个总条数第二个要查询起始记录数到查询的条数当第一次点击查询时候(非下一页时Page类里面预设的就是 index就是0 pageSize是预设值当点击下一页的时候 index 和 pageSize带的就是页面上面给的值了分页的页面一般的…

TypeError: Object of type ‘datetime‘ is not JSON serializable

python中这个错误的原因是json.dumps无法对字典中的datetime时间格式数据进行转化&#xff0c;dumps的原功能是将dict转化为str格式&#xff0c;不支持转化时间. 所以请这样使用&#xff1a; json.dumps(response_data, defaultstr)

oracle问题

ORA-01031: insufficient privileges 用户没有权限&#xff0c;给它赋予角色转载于:https://www.cnblogs.com/50614090/p/3986880.html

me23n去价格

SELECT knumv kposn AS ebelp kschl kbetr kpein kwert INTO CORRESPONDING FIELDS OF TABLE gt_konv FROM konv FOR ALL ENTRIES IN gt_ekpo WHERE knumv gt_ekpo-knumv AND kinak EQ AND kschl IN (PB00,PBXX,P101).转载于:…

Fix “Windows cannot access the specified device path or file” Error

http://helpdeskgeek.com/help-desk/windows-cannot-access-the-specified-device-path-or-file/ Method 1 – Windows Server 2003 Terminal Services Firstly, if you’re running into this issue on a Windows Server box running Terminal Services, your problem can be …

使用Bootstrap-table创建表单,并且与flask后台进行数据交互

文章目录引用css和js使用htmljavascriptflaskmysql参考引用css和js Bootstrap-table为这些文件提供了 CDN 的支持&#xff0c;所以不需要下载.js .css文件就可以直接用了&#xff0c;十分方便 <!-- Latest compiled and minified CSS --> <link rel"stylesheet…