当前位置: 首页 > news >正文

【Linux】进程间是这样通信的--管道篇

@TOC

目录

进程间通信的介绍

进程间通信的概念

进程间通信的目的

进程间通信的本质

进程间通信的分类

管道

什么是管道

匿名管道

pipe函数

匿名管道使用步骤

管道读写规则

管道的特点

1、管道内部自带同步与互斥机制

2、管道的生命周期随进程

3、管道提供的是流式服务

4、管道是半双工通信的

管道的四种特殊情况

管道的大小

方法一:使用man手册

方法二:使用ulimit命令

方法三:自行测试

命名管道

命名管道的原理

使用命令创建命名管道

创建一个命名管道

命名管道的打开规则

用命名管道实现serve&client通信

用命名管道实现派发计算任务

用命名管道实现文件拷贝

命令行当中的管道


进程间通信的介绍

进程间通信的概念

进程间通信简称IPC(Interprocess communication),进程间通信就是在不同进程之间传播或交换信息。

进程间通信的目的

  • 数据传输: 一个进程需要将它的数据发送给另一个进程。
  • 资源共享: 多个进程之间共享同样的资源。
  • 通知事件: 一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件,比如进程终止时需要通知其父进程。
  • 进程控制: 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信的本质

进程间通信的本质就是,让不同的进程看到同一份资源。

由于各个运行进程之间具有独立性,这个独立性主要体现在数据层面,而代码逻辑层面可以私有也可以公有(例如父子进程),因此各个进程之间要实现通信是非常困难的。

各个进程之间若想实现通信,一定要借助第三方资源,这些进程就可以通过向这个第三方资源写入或是读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域

因此,进程间通信的本质就是,让不同的进程看到同一份资源(内存,文件内核缓冲等)。 由于这份资源可以由操作系统中的不同模块提供,因此出现了不同的进程间通信方式。

进程间通信的分类

管道

  • 匿名管道
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

什么是管道

管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个1“管道”。

例如,统计我们当前使用云服务器上的登录用户个数。

其中,who命令和wc命令都是两个程序,当它们运行起来后就变成了两个进程,who进程通过标准输出将数据打到“管道”当中,wc进程再通过标准输入从“管道”当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理。

注明: who命令用于查看当前云服务器的登录用户(一行显示一个用户),wc -l用于统计当前的行数。

匿名管道

匿名管道用于进程间通信,且仅限于本地父子进程之间的通信。

进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。

注:

  • 这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。
  • 管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。

pipe函数

pipe函数用于创建匿名管道,pip函数的函数原型如下:

int pipe(int pipefd[2]);

pipe函数的参数是一个输出型参数,数组pipefd用于返回两个指向管道读端和写端的文件描述符:

数组元素含义
pipefd[0]管道读端的文件描述符
pipefd[1]管道写端的文件描述符

pipe函数调用成功时返回0,调用失败时返回-1。

源码如下:

struct pipe_buffer {struct page *page;unsigned int offset, len;const struct pipe_buf_operations *ops;unsigned int flags;unsigned long private;
};

其中我们的管道文件拥有一个缓冲区,这个缓冲区有一个专门的struct pipe_buf_operations结构体用来处理它的输入输出方法,以及flags用来标识当前缓冲区的装态

匿名管道使用步骤

在创建匿名管道实现父子进程间通信的过程中,需要pipe函数和fork函数搭配使用,具体步骤如下:

1、父进程调用pipe函数创建管道。

2、父进程创建子进程。

3、父进程关闭写端,子进程关闭读端。

注:

  1. 管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。
  2. 从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取。

我们可以站在文件描述符的角度再来看看这三个步骤:

1、父进程调用pipe函数创建管道

2、父进程创建子进程

3、父进程关闭写端,子进程关闭读端

例如,在以下代码当中,父进程向匿名管道当中写入10行数据,子进程从匿名管道当中将数据读出。

#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include "Log.hpp"
using namespace std;#define NUM 1024//匿名管道int main( )
{//创建管道int pipefd[2] = {0};if(pipe(pipefd) != 0){cerr << "pipe error" << endl;return 1;}//2.创建子进程pid_t id = fork();if(id < 0){cerr << "fork error" << endl;return 2;}else if(id == 0){//3.子进程管道// 子进程来进行读取,子进程就应该关掉写端close(pipefd[1]);char buffer[NUM];while(1){Log("等待写入", Debug) << endl;;memset(buffer, 0, sizeof(buffer));ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if(s > 0){//读取成功buffer[s] = '\0';cout << "子进程收到消息, 内容是: "<< buffer<< endl;}else if(s == 0){cout << "父进程写完了,子进程可以退出了"<< endl;break;}else {cerr <<"err while child read pipe "<< endl;}}close(pipefd[0]);exit(0);}else{//4.父进程管道//父进程来进行写入, 就应该关掉读端close(pipefd[0]);const char* msg = "我是父进程,这次发出的信息编号是";int cnt = 0;while(cnt < 10){char sendBuffer[1024];sprintf(sendBuffer, "%s : %d", msg, cnt);//格式化控制字符串write(pipefd[1], sendBuffer, strlen(sendBuffer));cnt++;cout << "cnt: "<< cnt << endl;sleep(1);}close(pipefd[1]);cout << "父进程输出完毕"<< endl;}//父进程等待子进程结束pid_t res = waitpid(id, nullptr, 0);if(res > 0){cout << "等待子进程成功" << endl;}cout << "子进程退出" <<endl;return 0;
}

这里我们可以查看一下代码,并没有sleep的停止语句,但是为什么在输出的时候,父进程输出一次,子进程输出一次,父进程休眠的时候,子进程看起来啥事没有做。实际上,子进程是在等待父进程对管道的写入。

管道读写规则

pipe2函数与pipe函数类似,也是用于创建匿名管道,其函数原型如下:

int pipe2(int pipefd[2], int flags);

pipe2函数的第二个参数用于设置选项。

1、当没有数据可读时:

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
2、当管道满的时候:

O_NONBLOCK disable:write调用阻塞,直到有进程读走数据。
O_NONBLOCK enable:write调用返回-1,errno值为EAGAIN。
3、如果所有管道写端对应的文件描述符被关闭,则read返回0。
4、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
5、当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。
6、当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。

管道的特点
1、管道内部自带同步与互斥机制

我们将一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行写入或是读取操作,因此管道也就是一种临界资源。

临界资源是需要被保护的,若是我们不对管道这种临界资源进行任何保护机制,那么就可能出现同一时刻有多个进程对同一管道进行操作的情况,进而导致同时读写、交叉读写以及读取到的数据不一致等问题。

为了避免这些问题,内核会对管道操作进行同步与互斥:

同步: 两个或两个以上的进程在运行过程中协同步调,按预定的先后次序运行。比如,A任务的运行依赖于B任务产生的数据。
互斥: 一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。
实际上,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。对于管道的场景来说,互斥就是两个进程不可以同时对管道进行操作,它们会相互排斥,必须等一个进程操作完毕,另一个才能操作,而同步也是指这两个不能同时对管道进行操作,但这两个进程必须要按照某种次序来对管道进行操作。

也就是说,互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,而同步的任务之间则有明确的顺序关系。
 

2、管道的生命周期随进程

管道本质上是通过文件进行通信的,也就是说管道依赖于文件系统,那么当所有打开该文件的进程都退出后,该文件也就会被释放掉,所以说管道的生命周期随进程。

3、管道提供的是流式服务

对于进程A写入管道当中的数据,进程B每次从管道读取的数据的多少是任意的,这种被称为流式服务,与之相对应的是数据报服务:

  • 流式服务: 数据没有明确的分割,不分一定的报文段。
  • 数据报服务: 数据有明确的分割,拿数据按报文段拿。
4、管道是半双工通信的

在数据通信中,数据在线路上的传送方式可以分为以下三种:

单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。
管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。

管道的四种特殊情况

在使用管道时,可能出现以下四种特殊情况:

        写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被挂起,直到管道里面有数据后,读端进程才会被唤醒。
读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么操作系统会将写端进程杀掉。
其中前面两种情况就能够很好的说明,管道是自带同步与互斥机制的,读端进程和写端进程是有一个步调协调的过程的,不会说当管道没有数据了读端还在读取,而当管道已经满了写端还在写入。读端进程读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中还有空间,若是条件不满足,则相应的进程就会被挂起,直到条件满足后才会被再次唤醒。

        第三种情况也很好理解,读端进程已经将管道当中的所有数据都读取出来了,而且此后也不会有写端再进行写入了,那么此时读端进程也就可以执行该进程的其他逻辑了,而不会被挂起。

        第四种情况也不难理解,既然管道当中的数据已经没有进程会读取了,那么写端进程的写入将没有意义,因此操作系统直接将写端进程杀掉。而此时子进程代码都还没跑完就被终止了,属于异常退出,那么子进程必然收到了某种信号。
我们可以通过以下代码看看情况四中,子进程退出时究竟是收到了什么信号。

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main(){int fd[2] = {0};if(pipe(fd) < 0){perror("pipe");return 1;}pid_t id = fork();//创建进程if(id == 0){//childclose(fd[0]); // 子进程关闭读段//子进程向管道写入数据const char* msg = "我是子进程";int count = 10;while(count--){write(fd[1], msg, strlen(msg));sleep (1);}close(fd[1]);exit(0);}//fatherclose(fd[1]);//父进程关闭写端close(fd[0]);//父进程直接关闭读端(导致子进程直接被操作系统杀掉)int status = 0;waitpid(id, &status, 0);printf("子进程获得的信号:%d\n",status & 0x7F);return 0;}

通过kill -l命令可以查看13对应的具体信号。

由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE信号将子进程终止的。

管道的大小

管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?

方法一:使用man手册

根据man手册,在2.6.11之前的Linux版本中,管道的最大容量与系统页面大小相同,从Linux 2.6.11往后,管道的最大容量是65536字节。

然后我们可以使用uname -r命令,查看自己使用的Linux版本。

根据man手册,我使用的是Linux 2.6.11之后的版本,因此管道的最大容量是65536字节。

方法二:使用ulimit命令

其次,我们还可以使用ulimit -a命令,查看当前资源限制的设定。

根据显示,管道的最大容量是 512 × 8 = 4096 512\times8=4096512×8=4096 字节。
 

方法三:自行测试

这里发现,根据man手册得到的管道容量与使用ulimit命令得到的管道容量不同,那么此时我们可以自行进行测试。

前面说到,若是读端进程一直不读取管道当中的数据,写端进程一直向管道写入数据,当管道被写满后,写端进程就会被挂起。据此,我们可以写出以下代码来测试管道的最大容量。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{int fd[2] = { 0 };if (pipe(fd) < 0){ //使用pipe创建匿名管道perror("pipe");return 1;}pid_t id = fork(); //使用fork创建子进程if (id == 0){//child close(fd[0]); //子进程关闭读端char c = 'a';int count = 0;//子进程一直进行写入,一次写入一个字节while (1){write(fd[1], &c, 1);count++;printf("%d\n", count); //打印当前写入的字节数}close(fd[1]);exit(0);}//fatherclose(fd[1]); //父进程关闭写端//父进程不进行读取waitpid(id, NULL, 0);close(fd[0]);return 0;
}

可以看到,在读端进程不进行读取的情况下,写端进程最多写65536字节的数据就被操作系统挂起了,也就是说,我当前Linux版本中管道的最大容量是65536字节。

命名管道

命名管道的原理


匿名管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间的通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。
如果要实现两个毫不相关进程之间的通信,可以使用命名管道来做到。命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

注:

  1. 普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。
  2. 命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

使用命令创建命名管道

我们可以使用mkfifo命令创建一个命名管道。

可以看到,创建出来的文件的类型是p,代表该文件是命名管道文件。

使用这个命名管道文件,就能实现两个进程之间的通信了。我们在一个进程(进程A)中用shell脚本每秒向命名管道写入一个字符串,在另一个进程(进程B)当中用cat命令从命名管道当中进行读取。
现象就是当进程A启动后,进程B会每秒从命名管道中读取一个字符串打印到显示器上。这就证明了这两个毫不相关的进程可以通过命名管道进行数据传输,即通信。
 

创建一个命名管道

在程序中创建命名管道使用mkfifo函数,mkfifo函数的函数原型如下:

int mkfifo(const char *pathname, mode_t mode);

mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。

  • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下。
  • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下。(注意当前路径的含义)

mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限。

例如,将mode设置为0666,则命名管道文件创建出来的权限如下:

但实际上创建出来文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来文件的权限为:mode&(~umask)。umask的默认值一般为0002,当我们设置mode值为0666时实际创建出来文件的权限为0664。

若想创建出来命名管道文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0。

umask(0); //将文件默认掩码设置为0

 mkfifo函数的返回值。

  • 命名管道创建成功,返回0。
  • 命名管道创建失败,返回-1。

创建命名管道示例:

使用以下代码即可在当前路径下,创建出一个名为myfifo的命名管道。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>#define FILE_NAME "myfifo"int main()
{umask(0); //将文件默认掩码设置为0if (mkfifo(FILE_NAME, 0666) < 0){ //使用mkfifo创建命名管道文件perror("mkfifo");return 1;}//create success...return 0;
}

运行代码后,命名管道myfifo就在当前路径下被创建了。

命名管道的打开规则

1、如果当前打开操作是为读而打开FIFO时。

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO。
  • O_NONBLOCK enable:立刻返回成功。

2、如果当前打开操作是为写而打开FIFO时。

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO。
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO。
     

用命名管道实现serve&client通信

实现服务端(server)和客户端(client)之间的通信之前,我们需要先让服务端运行起来,我们需要让服务端运行后创建一个命名管道文件,然后再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了。


服务端代码如下:

#include "comm.hpp"int main()
{umask(0);//1.创建管道文件if(mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功", Debug) << " step 1"<< endl;//2.正常的文件操作int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0 ){perror("open");exit(2);}Log("打开管道文件成功", Debug) << " step 2"<< endl;//3.编写正常的通信代码char buffer[SIZE];while(true){memset (buffer, '\0', sizeof(buffer));ssize_t s = read (fd, buffer,sizeof(buffer) - 1);if(s > 0){cout <<"["  << getpid() << "] "<< "client say> " << buffer << endl;}else if(s == 0){//end of filecerr <<"["  << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl;                                       break;}else{//read errorperror("read");break;}}//4.关闭文件close(fd);Log("关闭管道文件成功", Debug) << " step 3"<< endl;unlink(ipcPath.c_str());//通信完毕Log("删除管道文件成功", Debug) << " step 4"<< endl;return 0;
}  

而对于客户端来说,因为服务端运行起来后命名管道文件就已经被创建了,所以客户端只需以写的方式打开该命名管道文件,之后客户端就可以将通信信息写入到命名管道文件当中,进而实现和服务端的通信。

客户端的代码如下:

#include "comm.hpp"int main()
{//1.获取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}//2.ipc过程string buffer;while(true){cout << "Please Enter Message Line :>";std::getline(std::cin, buffer);write(fd, buffer.c_str(), buffer.size()); }//3. 关闭close(fd);return 0;
}

公用头文件:

#include <cstdio>
#include <cstdlib>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"using namespace std;#define MODE 0664
#define SIZE 128
string ipcPath = "./fifo.ipc";#endif

日志步骤记录

#ifndef _LOG_H_
#define _LOG_H_#include <iostream>
#include <ctime>
#include <string>
using namespace std;#define Notice 0
#define Warning 1
#define Error 2
#define Debug 3const std::string msg[] = { "Notice", "Warning", "Error", "Debug"};std::ostream &Log(std::string message, int level)
{std::cout <<" | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}#endif

代码编写完毕后,先将服务端进程运行起来,之后我们就能在客户端看到这个已经被创建的命名管道文件。

接着再将客户端也运行起来,此时我们从客户端写入的信息被客户端写入到命名管道当中,服务端再从命名管道当中将信息读取出来打印在服务端的显示器上,该现象说明服务端是能够通过命名管道获取到客户端发来的信息的,换句话说,此时这两个进程之间是能够通信的。

服务端和客户端之间的退出关系

当客户端退出后,服务端将管道当中的数据读完后就再也读不到数据了,那么此时服务端也就会去执行它的其他代码了(在当前代码中是直接退出了)。

当服务端退出后,客户端写入管道的数据就不会被读取了,也就没有意义了,那么当客户端下一次再向管道写入数据时,就会收到操作系统发来的13号信号(SIGPIPE),此时客户端就被操作系统强制杀掉了。

通信是在内存当中进行的

若是我们只让客户端向管道写入数据,而服务端不从管道读取数据,那么这个管道文件的大小会不会发生变化呢?

#include "comm.hpp"//server.c
int main()
{umask(0); //将文件默认掩码设置为0if (mkfifo(ipcPath.c_str(), 0666) < 0){ //使用mkfifo创建命名管道文件perror("mkfifo");return 1;}int fd = open(ipcPath.c_str(), O_RDONLY); //以读的方式打开命名管道文件if (fd < 0){perror("open");return 2;}while (1){//服务端不读取管道信息}close(fd); //通信完毕,关闭命名管道文件return 0;
}

可以看到,尽管服务端不读取管道当中的数据,但是管道当中的数据并没有被刷新到磁盘,使用ll命令看到命名管道文件的大小依旧为0,也就说明了双方进程之间的通信依旧是在内存当中进行的,和匿名管道通信是一样的。

用命名管道实现派发计算任务

需要注意的是两个进程之间的通信,并不是简单的发送字符串而已,服务端是会对客户端发送过来的信息进行某些处理的。

这里我们以客户端向服务端派发计算任务为例,客户端通过管道向服务端发送双操作数的计算请求,服务端接收到客户端的信息后需要计算出相应的结果。

这里我们无需更改客户端的代码,只需改变服务端处理通信信息的逻辑即可。

用命名管道实现文件拷贝

这里我们再用命名管道实现一下文件的拷贝

需要拷贝的文件是file.txt,该文件当中的内容如下:

我们要做的就是,让客户端将file.txt文件通过管道发送给服务端,在服务端创建一个file-bat.txt文件,并将从管道获取到的数据写入file-bat.txt文件当中,至此便实现了file.txt文件的拷贝。

其中服务端需要做的就是,创建命名管道并以读的方式打开该命名管道,再创建一个名为file-bat.txt的文件,之后需要做的就是将从管道当中读取到的数据写入到file-bat.txt文件当中即可。

服务端的代码如下:

#include "comm.hpp"//server.cint main()
{umask(0); //将文件默认掩码设置为0if (mkfifo(ipcPath.c_str(), 0666) < 0){ //使用mkfifo创建命名管道文件perror("mkfifo");return 1;}int fd = open(ipcPath.c_str(), O_RDONLY); //以读的方式打开命名管道文件if (fd < 0){perror("open");return 2;}//创建文件file-bat.txt,并以写的方式打开该文件int fdout = open("file-bat.txt", O_CREAT | O_WRONLY, 0666);if (fdout < 0){perror("open");return 3;}char msg[128];while (1){msg[0] = '\0'; //每次读之前将msg清空//从命名管道当中读取信息ssize_t s = read(fd, msg, sizeof(msg)-1);if (s > 0){write(fdout, msg, s); //将读取到的信息写入到file-bat.txt文件当中}else if (s == 0){cout <<"client quit!" << endl;break;}else{cout << "read error!" << endl;break;}}close(fd); //通信完毕,关闭命名管道文件close(fdout); //数据写入完毕,关闭file-bat.txt文件return 0;
}

而客户端需要做的就是,以写的方式打开这个已经存在的命名管道文件,再以读的方式打开file.txt文件,之后需要做的就是将file.txt文件当中的数据读取出来并写入管道当中即可。

客户端的代码如下:

#include "comm.hpp"int main()
{int fd = open(ipcPath.c_str(), O_WRONLY);//以写的方法打开命令管道文件if(fd < 0){perror("open");return 1;}int fdin = open("file.txt", O_RDONLY);if(fdin < 0){perror("open");return 2;}char msg[128];while(1){//从file.txt文档中读取数据ssize_t s = read(fdin, msg, sizeof (msg));if(s > 0){write(fd, msg, s);//将读取到的数据写入到命名管道中}else if(s == 0){cout << "read end of file!" <<endl;break;}else{cout << "read error"<< endl;break;}}close( fd);close(fdin);return 0;}

编写完代码后,先运行服务端,再运行客户端,一瞬间这两个进程就相继运行结束了。

查看这个目录,就会发现一个file.txt文件的拷贝。

内容和file.txt一样

使用管道实现文件的拷贝有什么意义?

因为这里是使用管道在本地进行的文件拷贝,所以看似没什么意义,但我们若是将这里的管道想象成“网络”,将客户端想象成“Windows Xshell”,再将服务端想象成“centos服务器”。那我们此时实现的就是文件上传的功能,若是将方向反过来,那么实现的就是文件下载的功能。

命名管道和匿名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,由open函数打开。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。

命令行当中的管道

现有file.txt文件,文件当中的内容如下:

我们可以利用管道(“|”)同时使用cat命令和grep命令,进而实现文本过滤。

这里的 | 就是命令行当中的管道

由于匿名管道只能用于有亲缘关系的进程之间的通信,而命名管道可以用于两个毫不相关的进程之间的通信,因此我们可以先看看命令行当中用管道(“|”)连接起来的各个进程之间是否具有亲缘关系。

下面通过管道(“|”)连接了三个进程,通过ps命令查看这三个进程可以发现,这三个进程的PPID是相同的,也就是说它们是由同一个父进程创建的子进程。


而它们的父进程实际上就是命令行解释器,这里为bash

也就是说,由管道(“|”)连接起来的各个进程是有亲缘关系的,它们之间互为兄弟进程。

现在我们已经知道了,若是两个进程之间采用的是命名管道,那么在磁盘上必须有一个对应的命名管道文件名,而实际上我们在使用命令的时候并不存在类似的命名管道文件名,因此命令行上的管道实际上是匿名管道。

相关文章:

【Linux】进程间是这样通信的--管道篇

TOC 目录 进程间通信的介绍 进程间通信的概念 进程间通信的目的 进程间通信的本质 进程间通信的分类 管道 什么是管道 匿名管道 pipe函数 匿名管道使用步骤 管道读写规则 管道的特点 1、管道内部自带同步与互斥机制 2、管道的生命周期随进程 3、管道提供的是流式…...

Python基础入门例程60-NP60 跳过列表的某个元素(循环语句)

最近的博文: Python基础入门例程59-NP59 提前结束的循环(循环语句)-CSDN博客 Python基础入门例程58-NP58 找到HR(循环语句)-CSDN博客 Python基础入门例程57-NP57 格式化清单(循环语句)-CSDN博客 目录 最近的博文: 描述...

三十二、W5100S/W5500+RP2040树莓派Pico<UPnP示例>

文章目录 1 前言2 简介2 .1 什么是UPnP&#xff1f;2.2 UPnP的优点2.3 UPnP数据交互原理2.4 UPnP应用场景 3 WIZnet以太网芯片4 UPnP示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着智能家居、物联网等…...

2023.11.18 Hadoop之 YARN

1.简介 Apache Hadoop YARN &#xff08;Yet Another Resource Negotiator&#xff0c;另一种资源协调者&#xff09;是一种新的 Hadoop 资源管理器&#xff0c;它是一个通用资源管理系统和调度平台&#xff0c;可为上层应用提供统一的资源管理和调度。支持多个数据处理框架&…...

ceph 常用命令

bucket 常用命令 查看 realm &#xff08;区域&#xff09; radosgw-admin realm list输出 {"default_info": "43c462f5-5634-496e-ad4e-978d28c2x9090","realms": ["myrgw"] }radosgw-admin realm get{"id": "2cfc…...

6.8完全二叉树的节点个数(LC222-E)

算法&#xff1a; 如果不考虑完全二叉树的特性&#xff0c;直接把完全二叉树当作普通二叉树求节点数&#xff0c;其实也很简单。 递归法&#xff1a; 用什么顺序遍历都可以。 比如后序遍历&#xff08;LRV&#xff09;&#xff1a;不断遍历左右子树的节点数&#xff0c;最后…...

基于协作mimo系统的RM编译码误码率matlab仿真,对比硬判决译码和软判决译码

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ..................................................................... while(Err < TL…...

Django模型层

模型层 与数据库相关的&#xff0c;用于定义数据模型和数据库表结构。 在Django应用程序中&#xff0c;模型层是数据库和应用程序之间的接口&#xff0c;它负责处理所有与数据库相关的操作&#xff0c;例如创建、读取、更新和删除记录。Django的模型层还提供了一些高级功能 首…...

计算机视觉的应用18-一键抠图人像与更换背景的项目应用,可扩展批量抠图与背景替换

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用18-一键抠图人像与更换背景的项目应用&#xff0c;可扩展批量抠图与背景替换。该项目能够让你轻松地处理和编辑图片。这个项目的核心功能是一键抠图和更换背景。这个项目能够自动识别图片中的主体&…...

Redis(哈希Hash和发布订阅模式)

哈希是一个字符类型字段和值的映射表。 在Redis中&#xff0c;哈希是一种数据结构&#xff0c;用于存储键值对的集合。哈希可以理解为一个键值对的集合&#xff0c;其中每个键都对应一个值。哈希在Redis中的作用主要有以下几点&#xff1a; 1. 存储对象&#xff1a;哈希可以用…...

php正则表达式汇总

php正则表达式有"/pattern/“、”“、”$“、”.“、”[]“、”[]“、”[a-z]“、”[A-Z]“、”[0-9]“、”\d"、“\D”、“\w”、“\W”、“\s”、“\S”、“\b”、“*”、“”、“?”、“{n}”、“{n,}”、“{n,m}”、“\bword\b”、“(pattern)”、“x|y"和…...

Python与ArcGIS系列(八)通过python执行地理处理工具

目录 0 简述1 脚本执行地理处理工具2 在地理处理工具间建立联系0 简述 arcgis包含数百种可以通过python脚本执行的地理处理工具,这样就通过python可以处理复杂的工作和批处理。本篇将介绍如何利用arcpy实现执行地理处理工具以及在地理处理工具间建立联系。 1 脚本执行地理处理…...

cocos----刚体

刚体&#xff08;Rigidbody&#xff09; 刚体&#xff08;Rigidbody&#xff09;是运动学&#xff08;Kinematic&#xff09;中的一个概念&#xff0c;指在运动中和受力作用后&#xff0c;形状和大小不变&#xff0c;而且内部各点的相对位置不变的物体。在 Unity3D 中&#xff…...

【SAP-HCM】--HR人员信息导入函数

人员基本信息导入函数&#xff1a;HR_MAINTAIN_MASTERDATA 人员其他信息类型导入函数&#xff1a;HR_INFOTYPE_OPERATION 不逼逼&#xff0c;直接上代码&#xff0c;这两个函数还是相对简单易懂的 *根据操作类型查找对应的T529A 操作类型对应的值IF gt_alv IS NOT INITIAL.S…...

【开源】基于JAVA的大学兼职教师管理系统

项目编号&#xff1a; S 004 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S004&#xff0c;文末获取源码。} 项目编号&#xff1a;S004&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容三、界面展示3.1 登录注册3.2 学生教师管…...

Pyhon函数

import time # # for i in range(1,10): # j1 # for j in range(1,i1): # print(f"{i}x{j}{i*j} " ,end) # print() #复用&#xff0c;代码&#xff0c;精简&#xff0c;复用度高def j99(n1,max10): for i in range(n,max):jifor j in ran…...

使用vuex完成小黑记事本案例

使用vuex完成小黑记事本案例 App.vue <template><div id"app"><TodoHeader></TodoHeader><TodoMain ></TodoMain><TodoFooter></TodoFooter></div> </template><script> import TodoMain from …...

进阶理解:leetcode115.不同的子序列(细节深度)

这道题是困难题&#xff0c;本章是针对于动态规划解决&#xff0c;对于思路进行一个全面透彻的讲解&#xff0c;但是并不是对于基础讲解思路&#xff0c;而是渗透到递推式和dp填数的详解&#xff0c;如果有读者不清楚基本的解题思路&#xff0c;请看我的这篇文章算法训练营DAY5…...

数据结构-哈希表(C语言)

哈希表的概念 哈希表就是&#xff1a; “将记录的存储位置与它的关键字之间建立一个对应关系&#xff0c;使每个关键字和一个唯一的存储位置对 应。” 哈希表又称&#xff1a;“散列法”、“杂凑法”、“关键字&#xff1a;地址法”。 哈希表思想 基本思想是在关键字和存…...

HCIA-综合实验(三)

综合实验&#xff08;三&#xff09; 1 实验拓扑2 IP 规划3 实验需求一、福州思博网络规划如下&#xff1a;二、上海思博网络规划如下&#xff1a;三、福州思博与上海思博网络互联四、网络优化 4 配置思路4.1 福州思博配置在 SW1、SW2、SW3 上配置交换网络SW1、SW2、SW3 运行 S…...

Java程序员的成长路径

熟悉JAVA语言基础语法。 学习JAVA基础知识&#xff0c;推荐阅读书单中的经典书籍。 理解并掌握面向对象的特性&#xff0c;比如继承&#xff0c;多态&#xff0c;覆盖&#xff0c;重载等含义&#xff0c;并正确运用。 熟悉SDK中常见类和API的使用&#xff0c;比如&#xff1…...

几种常用的排序

int[] arr new int[]{1, 2,8, 7, 5};这是提前准备好的数组 冒泡排序 public static void bubbleSort(int[] arr) {int len arr.length;for (int i 0; i < len - 1; i) {for (int j 0; j < len - i - 1; j) {if (arr[j] > arr[j1]) {int temp arr[j];arr[j] ar…...

性能测试【第三篇】Jmeter的使用

线程数:10 ,设置10个并发 Ramp-Up时间(秒):所有线程在多少时间内启动,如果设置5,那么每秒启动2个线程 循环次数:请求的重复次数,如果勾选"永远"将一直发送请求 持续时间时间:设置场景运行的时间 启动延迟:设置场景延迟启动时间 响应断言 响应断言模式匹配规则 包括…...

业务:业务系统检查项参考

名录明细云平台摸底1.原有云平台体系&#xff1a;VMware、openstack、ovirt、k8s、docker、混合云系列及版本 2.原有云平台规模&#xff0c;物理机数量、虚拟机数量、迁移业务系统所占配额 3.待补充系统摸底 (适用于物理主机)每一台虚拟机或物理机&#xff1a; 1.系统全局参数…...

解决公网下,k8s calico master节点无法访问node节点创建的pod

目的&#xff1a;解决pod部署成功后&#xff0c;只能在node节点访问&#xff0c;而master节点无法访问 原因&#xff1a;集群搭建时&#xff0c;没有配置公网进行kubectl操作&#xff0c;从而导致系统默认node节点&#xff0c;使用内网IP加入k8s集群&#xff01;如下&#xff…...

六边形架构

Alistair Cockburn是于1953年出生在美国的一位软件开发方法学家。他毕业于康奈尔大学计算机科学专业&#xff0c;并获得了博士学位。 Cockburn在敏捷软件开发领域做出了许多重要的贡献&#xff0c;他被广泛认可为敏捷方法学的奠基人之一。他提出了许多关于敏捷开发的原则和实践…...

基于单片机的智能家居安保系统(论文+源码)

1.系统设计 本次基于单片机的智能家居安保系统设计&#xff0c;在功能上如下&#xff1a; 1&#xff09;以51单片机为系统控制核心&#xff1b; 2&#xff09;温度传感器、人体红外静释电、烟雾传感器来实现检测目的&#xff1b; 3&#xff09;以GSM模块辅以按键来实现远/近程…...

盘点3种Python网络爬虫过程中的中文乱码的处理方法

网络爬虫过程中三种中文乱码的处理方案&#xff0c;希望对大家的学习有所帮助 一、思路 其实解决问题的关键点就是在于一点&#xff0c;就是将乱码的部分进行处理&#xff0c;而处理的方案主要可以从两个方面进行出发。其一是针对整体网页进行提前编码&#xff0c;其二是针对…...

小程序富文本图片大小问题

文章目录 概要uniapp小程序情况解决方法及完整示例 概要 在小程序使用富文本或者在nuiapp&#xff08;小程序的&#xff09;使用富文本都会转为 <rich-text nodes"<p class"p class">内容</p>”></rich-text>如果是这种情况的话在css…...

Diagrams——制作短小精悍的流程图

今天为大家分享的是一款轻量级的流程图绘制软件——Diagrams。 以特定的图形符号加上说明&#xff0c;表示算法的图&#xff0c;称为流程图或框图。流程图是流经一个系统的信息流、观点流或部件流的图形代表。我们常用流程图来说明某一过程。 流程图使用一些标准符号代表某些类…...

Elasticsearch基础条件查询

条件查询 query&#xff1a;查询 match&#xff1a;匹配 match_all&#xff1a;匹配所有 #第一种 GET /shopping/_search?q名字:张三#第二种 GET /shopping/_search {"query": {"match": {"名字": "张三"}} }#全量查询 match_all G…...

【SAP-ABAP】SAP与外围系统对接方式

SAP作为接受方 1.JAVA直接配置IP、账号、密码&#xff08;有些人叫SAPWEBAPI&#xff09;调用SAP里面的RFC函数 2.SAP发布RFC函数&#xff0c;配置webservice地址 3.SAP发布ODATA服务 4.SAP发布restful的http服务 5.我不太懂的UI5和FIORI本质好像也是odata服务 6.IDOC SA…...

云计算的发展趋势

云计算的发展一直是一个极具活力和创新的领域。随着技术的不断进步和应用场景的拓展&#xff0c;云计算将在未来继续发挥重要作用。以下是云计算发展的一些趋势&#xff1a; 边缘计算的崛起&#xff1a; 随着物联网设备的普及和应用场景的增多&#xff0c;边缘计算成为一个重要…...

国民技术Cortex-M0系列单片机IAP升级

考虑到设备部署到现场后有可能需要进行软件升级&#xff0c;之前做过PIC系列单片机的升级&#xff0c;现在想做个国民技术N32G031系列Cortex-M0内核的单片机IAP方案。 因为国民技术系列单片机在很多大程度上都模仿了STM32&#xff0c;所以我想其升级方案极有可能差不多。于是在…...

Pycharm中添加Python库指南

一、介绍 Pycharm是一款为Python开发者提供的集成开发环境&#xff08;IDE&#xff09;&#xff0c;支持执行、调试Python代码&#xff0c;并提供了许多有用的工具和功能&#xff0c;其中之一就是在Pycharm中添加Python库。 添加Python库有许多好处&#xff0c;比如能够增加开…...

Oracle OCP / MySQL OCP认证容易通过吗

诸多学员在首次考OCP时&#xff0c;不清楚要如何选择。在本文中&#xff0c;我会为大家进行讲解&#xff01; 选择OCP认证时需要考虑的几大项目&#xff1a; 授课老师师资经验 课程大纲 试听课程 考试通过率 业界口碑 服务质量 郭一军老师的OCP培训在业界培训的学员中已…...

flutter web 中嵌入一个html

介绍 flutter web 支持使用 HtmlElementView嵌入html import dart:html; import dart:ui as ui; import package:flutter/cupertino.dart;class WebWidget extends StatelessWidget {const WebWidget({super.key});overrideWidget build(BuildContext context) {DivElement fr…...

使用Spark SQL读取阿里云OSS的数据

读OSS数据 创建一个table&#xff0c;并关联OSS目录路径 CREATE TABLE my_table USING parquet OPTIONS (path oss://my_bucket/my_data_dir/dt20230904, -- 关联OSS路径header true, -- 如果Parquet文件包含列名的头部信息&#xff0c;则设置为true&#xff0c;否则设置为f…...

【0235】修改私有内存(private memory)中的MyBEEntry时,st_changecount值前后变化

上一篇: 【0234】PgBackendStatus 记录当前postgres进程的活动状态 1. pg_stat_activity中xxx实时信息如何实现? 客户端(eg:psql)在连接上postmaster之后,postmaster守护进程会fork()一个后端进场(backend process),之后此客户端的所有操作、交互均有此对应的Backen…...

Linux学习命令之source

在Linux中&#xff0c;source命令用于在当前shell环境中执行指定脚本文件中的命令。它通常用于重新加载修改过的shell配置文件&#xff0c;例如.bashrc或.profile&#xff0c;以使更改生效&#xff0c;而不需要退出当前的shell会话。 使用方法为&#xff1a; source 文件路径…...

2342. 数位和相等数对的最大和

我的解法&#xff1a; 对数组进行排序&#xff0c;最大数一定最先进入哈希表进行加和 class Solution { public:int maximumSum(vector<int>& nums) {unordered_map<int, vector<int>> h;int ans -1;sort(nums.begin(), nums.end());for (int i nums.…...

FISCO BCOS 3.0【01】搭建第一个区块链网络

官方技术文档&#xff1a;https://fisco-bcos-doc.readthedocs.io/zh-cn/latest/index.html 我们在官方技术文档的基础上&#xff0c;进行&#xff0c;对文档中一些不清楚的地方进行修正 搭建Air版本FISCO BCOS联盟链 本节以搭建单群组FISCO BCOS链为例操作&#xff0c;使用开…...

UE4动作游戏实例RPG Action解析四:装备系统

导语: 以加血道具为例,详细分析拆解ActionRPG的装备系统,包含装备系统需求和数据结构设计,以及实现 一、装备系统需求: 装备槽: 已获取装备和未获取装备: 当已经装备一个道具时,再次捡到道具,会把道具放在装备库,不会放在装备槽中, 当没有装备道具时,会拾取道具…...

AIGC之Stable Diffusion

AIGC是什么? AIGC:Artificial Intelligence Generated Content,生成式人工智能。通俗一点来讲,对AI下达指令任务,通过处理人的自然语言,自动生成图片、视频、音频等等。 Stable Diffusion 官网:https://stablediffusionweb.com/ 介绍:stablediffusionweb.com is an eas…...

PHP接收并处理请求中携带的xml格式的信息

企业微信以xml的形式给服务器发送通知。具体参考&#xff1a;企业微信文档——事件格式 dump(file_get_contents("php://input"));/***string(530) "<xml><ToUserName><![CDATA[ww55ca070cb9b7eb22]]></ToUserName><FromUserName>…...

信息安全相关标准

GB/T 25070-2019 信息安全技术 网络安全等级保护安全设计技术要求 在线预览|GB/T 25070-2019 (gb688.cn) 每一级安全保护环境分为安全计算环境&#xff0c;安全区域边界&#xff0c;安全通信网络。 一级通用安全计算环境 二级通用安全 计算环境 三级 通用安全计算环境 第四级…...

Python入门学习篇(一)——注释变量输入输出

1 注释 1.1 作用 a 方便他人和自己阅读代码 b 告诉编译器这部分内容是不用执行的。1.2 单行注释 # 注释内容1.3 多行注释(引号) 1.3.1 三对双引号 """ 注释内容 """1.3.2 三对单引号 注释内容 1.4 pycharm快捷键使用 ctrl/ 多行注释(以# …...

基于单片机智能液位水位监测控制系统设计

**单片机设计介绍&#xff0c; 基于单片机智能液位水位监测控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能液位水位监测控制系统可以用来检测和控制液位的高低&#xff0c;并可以用于水泵的控制和自…...

iOS 添加震动效果

1. AudioServicesPlaySystemSound 较早的系统版本&#xff0c;我们会使用AudioTool.framework。 #import <AudioToolbox/AudioToolbox.h>一般震动 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);普通短震&#xff08;类似3D Touch的 Peek 反馈 &#xff09; …...

合并word中参考文献-(Endnote生成)

合并word中的 两部分的参考文献引用 Merge Citations in the Word document Original&#xff1a; A is a big character [78-80] and B is another one [81-85] Modified&#xff1a; A and B are big characters [78-85] Solutions&#xff1a; Remove the space betwee…...