【Linux从入门到精通】通信 | 管道通信(匿名管道 命名管道)

 

  本派你文章主要是对进程通信进行详解。主要内容是介绍 为什么通信、怎么进行通信。其中本篇文章主要讲解的是管道通信。希望本篇文章会对你有所帮助。

文章目录

一、进程通信简单介绍

1、1 什么是进程通信

1、2 为什么要进行通信

 1、3 进程通信的方式

二、匿名管道

2、1 什么是管道

2、2 匿名管道通信

2、3 管道通信 demo代码

2、3、1 pipe 创建管道

2、3、2 demo 代码

2、4 匿名管道特点

2、5 进程池

2、5、1 Tasks.hpp

2、5、2 ProcessPool.cpp

2、5、3 demo 代码解释

三、命名管道

3、1 什么是命名管道

3、2 命名管道通信

3、3 命名管道 demo 代码

3、3、1 mkfifo

3、3、2 demo 代码

四、总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:管道通信💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️ 

一、进程通信简单介绍

1、1 什么是进程通信

  进程通信是指不同进程之间进行数据交换、消息传递和协作的过程。在操作系统中,每个进程都是独立运行的单位,它们拥有各自的内存空间和执行环境。为了实现进程之间的互动和合作,需要通过进程通信来进行数据共享、状态同步、任务协作等操作。

  我们知道进程都是独立的,各自有各自的地址空间。而进程通信的本质是让不同的进程能够看到同一块“内存”。而这块内存并不属于任何一个进程,是所有进程共享的

1、2 为什么要进行通信

  我们之前学习的都是单进程。单进程不能使用并发能力,更无法实现多进程协同。下面给出需要进程通信的原因:

  1. 数据共享和传递:不同的进程可能需要共享数据,例如一个进程产生的结果可能需要被其他进程使用。通过进程间通信,可以实现数据的传递和共享,让不同的进程能够获取彼此的数据,并保持数据的一致性。

  2. 任务协作和协调:在复杂的应用程序中,多个进程往往需要协同工作完成某个任务。通过进程间通信,进程可以互相发送消息和指令,协调彼此的行动,实现任务的划分、分工和协作。

  3. 资源共享和管理:在计算机系统中,各个进程需要共享有限的资源,如内存、文件、设备等。进程间通信能够确保多个进程正确地访问和管理共享资源,避免冲突和资源浪费。

  4. 进程控制和同步:进程间通信提供了一种机制,使得进程能够进行进程间的控制和同步操作。例如,一个进程可能需要等待另一个进程完成某个任务后才能继续执行,通过进程间通信,可以实现进程的阻塞和唤醒,实现进程的协调。

 1、3 进程通信的方式

常见的进程通信方法包括以下几种:

  1. 管道(Pipe):管道提供了一种半双工的、单向的通信机制,通常用于具有父子关系的进程之间进行通信。

  2. 消息队列(Message Queue):消息队列是一种使用消息缓冲区进行通信的形式,进程可以把消息发送到队列中,然后其他进程从队列中读取消息。

  3. 共享内存(Shared Memory):共享内存是一种将一块内存区域映射到多个进程的机制,多个进程可以直接访问这块共享内存,实现高效的数据共享。

  4. 信号量(Semaphore):信号量是一种用于进程之间同步和互斥的机制,可以通过提供一个计数器,控制多个进程对共享资源的访问。

  管道通信又分为匿名管道和命名管道通信。本篇文章讲解的重点就是管道通信。

二、匿名管道

2、1 什么是管道

  在Linux中,管道是一种用于进程间通信的特殊文件。它可以连接一个进程的输出到另一个进程的输入,实现数据的传输和共享。管道通信是一种基于管道的进程间通信方式。

  举一个具体的例子来解释一下如何使用管道和进行管道通信:假设有两个命令,command1和command2,我们希望将command1的输出传递给command2进行处理。首先,我们可以使用管道符号|将这两个命令连接起来。具体如下:

command1 | command2

2、2 匿名管道通信

  匿名管道顾名思义:没有名字的管道。匿名管道只能在具有亲缘关系的进程间使用,通常用于父进程和子进程之间进行通信。我们上面了解了管道是一个文件,文件不都是有名字的吗?在磁盘上的文件都是有名字的。但是匿名管道并不是在磁盘上,而是在内存中存储。需要注意的是,匿名管道的数据是临时存储在内存中的,而不是永久保存在磁盘上。当相关的进程结束时,管道和其中的数据也会被释放,不会留下任何痕迹。那我们接下来看看是怎么进行通信的。

  管道通信本质上就是进程通信。也可以理解为进程通信的手段是利用了管道。我们创建一个进程,以读写的形式打开一个文件。具体如下图:

  然后我们再创建一个子进程。我们知道子进程会继承父进程的相关代码个数据结构的。创建完子进程后,具体如下图:

 

  我们知道相关的数据结构会继承,但是文件也会被继承吗(就是所指向的文件也会被拷贝一份吗)?并不会的。这时候不就是让不同的进程看到了同一块内存资源吗!!!管道是单向通信的机制。然后我们再关闭我们不需要的文件描述符(fd),不就是我们所说的管道吗!!!

  从上述过程我们发现了, 创建匿名管道分为以下三个步骤:

  1. 分别以读写的方式打开同一个文件;
  2. fork()创建子进程;
  3. 父子进程各自关闭自己不需要的文件描述符(fd)。

  下面我们不妨模拟一下管道通信,是我们的理解更加深刻。

2、3 管道通信 demo代码

2、3、1 pipe 创建管道

  我们怎么同时以读写的方式打开一个文件呢?可以利用一个系统调用函数:pipe。

  pipe函数是一种创建管道的系统调用。它被用于在进程间进行通信,使得一个进程的输出能够直接成为另一个进程的输入。我们来看一下pipe函数的使用。

  参数 pipefd[2] 是输出型参数。调用成功后,会将所打开文件的读端、写端的文件描述符写入该数组。pipefd[0]用于读取管道数据,pipefd[1]用于写入管道数据。我们不妨来测试一下pipefd 数组中是否获得了所打开文件的文件描述符。代码如下:

  #include<iostream>    #include<unistd.h>    #include<assert.h>    using namespace std;    int main()    {    int pipefd[2];    int n=pipe(pipefd);    assert(n != -1);    cout<< "pipefd[0] : "<<pipefd[0]<<endl;    cout<< "pipefd[1] : "<<pipefd[1]<<endl;    return 0;    }

  我们来看一下输出结果:

  根据我们之前学的文件描述符章节,确实是将新打开的文件描述符写入了数组。加入我们是想要父进程写,子进程来读。那我们就可以让父子进程各自进行关闭对应的不需要的文件描述符了。具体结合下图理解:

2、3、2 demo 代码

  对上述的了解后,我们大概知道了匿名管道通信的过程。那么下面我们看一下demo代码:

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<cstring>
#include<string>
#include<sys/wait.h>
#include<assert.h>using namespace std;int main()
{int pipefd[2]; //默认情况 pipefd[0]:读端口、pipefd[1]:写端口int n=pipe(pipefd);assert(n!=-1);(void)n;pid_t id=fork();assert(id!=-1);// 父写 子读if(id==0){//子进程//构建单向通信管道,关闭不需要的端口close(pipefd[1]);char buffer[1024];while(true){ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);if(s > 0){buffer[s]=0;cout << "child read successfully:[" << getpid() << "]<< Fatehr#" << buffer<<endl;}}close(pipefd[0]);exit(0);}//父进程close(pipefd[0]);string message="我是父进程,我正在发消息";int count=0;char send_buffer[1024];while(true){snprintf(send_buffer,sizeof(send_buffer),"%s[%d]:%d",message.c_str(),getpid(),count++);write(pipefd[1],send_buffer,strlen(send_buffer));sleep(1);}pid_t ret=waitpid(id,nullptr,0);assert(ret>0);(void)ret;close(pipefd[1]);return 0;
}

  我们对上述demo代码的思路进行解释:

  1. 首先包含了一些必要的头文件,包括输入输出流、进程管理和管道相关的系统调用等。然后定义了一个整型数组pipefd,用于存储管道的读写端口pipe(pipefd)创建了一个匿名管道,并将读端口和写端口的文件描述符存储在pipefd数组中

  2. 接下来,通过fork()函数创建了一个子进程。子进程执行的代码是在if(id==0)条件中(子进程),首先关闭了写端口pipefd[1],然后进入一个无限循环。在循环中,子进程通过read()函数从管道的读端口pipefd[0]读取数据。如果读取成功,就将读取到的数据存储在buffer中,并在控制台上打印输出。

  3. 在父进程中,首先关闭了读端口pipefd[0](因为父进程不需要从管道中读取数据),然后进入一个无限循环。在循环中,父进程将包含有特定格式信息的字符串存储在send_buffer中,然后通过write()函数将send_buffer中的数据写入管道的写端口pipefd[1]。发送完毕后,父进程通过sleep(1)函数暂停1秒。

  4. 最后,父进程通过waitpid()等待子进程结束,并关闭写端口pipefd[1]。整个程序执行完成后,返回0。总结:该程序利用了管道(pipe)实现了父子进程之间的单向通信。父进程向管道写入消息,子进程从管道读取消息并输出。

  上述不过成就模拟出了父子进程通过匿名管道进行了数据交互,我们也称之为进程间通信。

2、4 匿名管道特点

  我们接下来再总结一下管道的特点:

  1. 管道是用来进行具有血缘关系的进程进性进程间通信——常用于父子通信;
  2. 管道具有通过让进程间协同,提供了访问控制!
  3. 管道提供的是面向流式的通信服务——面向字节流——协议;
  4. 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的!
  5. 管道是单向通信的,就是半双工通信的一种特殊情况;

   上述特点中说到管道提供了访问控制,什么是访问控制呢?就上述我们的管导通信 demo代码来说,父进程是每隔一秒向管到文件中写入一条数据。而子进程并没有进行休眠,是一直处于读的状态。那运行结果是什么情况呢?如下图:

  通过观察上述情况,我们也不难发现子进程也是没休眠一秒进程再读取数据,跟父进程的写数据是同步的。这是为什么呢?原因就是管道提供了访问控制。当管道没有数据时,读端就要进行等待,等待写端写入数据后再进行读取!!!我们接下来总结一下管道访问控制的特点:

  1. 写快,读慢,写满不能在写了;
  2. 写慢,读快,管道没有数据的时候,读必须等待;
  3. 写关,读0,标识读到了文件结尾;
  4. 读关,写继续写,OS终止写进程。

2、5 进程池

  此小节是一个扩展,可以加强理解学习。我们知道一个进程可能会处理很多任务。但是一个父进程只能建立与其对应的唯一一个管道吗?其实并不是的。一个父进程可以建立多个子进程,那么对应的是不是就可以创建多个管道了!具体如下图:

  那这样的话,我们父进程是不是就可以将任务派发个子进程来处理,这样是不是就可以提高了效率!我们接下来看一下我们上述所讲的 demo 代码。

2、5、1 Tasks.hpp

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>typedef std::function<void()> func;std::vector<func> callbacks;
std::unordered_map<int, std::string> desc;void readMySQL()
{std::cout << "sub process[" << getpid() << " ] 执行访问数据库的任务\n" << std::endl;
}void execuleUrl()
{std::cout << "sub process[" << getpid() << " ] 执行url解析\n" << std::endl;
}void cal()
{std::cout << "sub process[" << getpid() << " ] 执行加密任务\n" << std::endl;
}void save()
{std::cout << "sub process[" << getpid() << " ] 执行数据持久化任务\n" << std::endl;
}void load()
{desc.insert({callbacks.size(), "readMySQL: 读取数据库"});callbacks.push_back(readMySQL);desc.insert({callbacks.size(), "execuleUrl: 进行url解析"});callbacks.push_back(execuleUrl);desc.insert({callbacks.size(), "cal: 进行加密计算"});callbacks.push_back(cal);desc.insert({callbacks.size(), "save: 进行数据的文件保存"});callbacks.push_back(save);
}void showHandler()
{for(const auto &iter : desc ){std::cout << iter.first << "\t" << iter.second << std::endl;}
}int handlerSize()
{return callbacks.size();
}

2、5、2 ProcessPool.cpp

#include "Task.hpp"#define PROCESS_NUM 5using namespace std;int waitCommand(int waitFd, bool &quit) //如果对方不发,我们就阻塞
{uint32_t command = 0;ssize_t s = read(waitFd, &command, sizeof(command));if (s == 0){quit = true;return -1;}assert(s == sizeof(uint32_t));return command;
}void sendAndWakeup(pid_t who, int fd, uint32_t command)
{write(fd, &command, sizeof(command));cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
}int main()
{// 代码中关于fd的处理,有一个小问题,不影响我们使用,但是你能找到吗??load();// pid: pipefdvector<pair<pid_t, int>> slots;// 先创建多个进程for (int i = 0; i < PROCESS_NUM; i++){// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();assert(id != -1);// 子进程我们让他进行读取if (id == 0){// 关闭写端close(pipefd[1]);// childwhile (true){// pipefd[0]// 等命令bool quit = false;// 等待任务派发int command = waitCommand(pipefd[0], quit); //如果对方不发,我们就阻塞if (quit)break;// 执行对应的命令if (command >= 0 && command < handlerSize()){callbacks[command]();}else{cout << "非法command: " << command << endl;}}exit(1);}// father,进行写入,关闭读端close(pipefd[0]); // pipefd[1]slots.push_back(pair<pid_t, int>(id, pipefd[1]));}// 父进程派发任务srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 让数据源更随机while (true){// 自动派发任务// 选择一个任务int command = rand() %  handlerSize();// 选择一个进程 ,采用随机数的方式,选择进程来完成任务,随机数方式的负载均衡int choice = rand() % slots.size();// 把任务给指定的进程sendAndWakeup(slots[choice].first, slots[choice].second, command);sleep(1);// 手动选择派发任务// int select;// int command;// cout << "############################################" << endl;// cout << "#   1. show funcitons      2.send command  #" << endl;// cout << "############################################" << endl;// cout << "Please Select> ";// cin >> select;// if (select == 1)//     showHandler();// else if (select == 2)// {//     cout << "Enter Your Command> ";//     // 选择任务//     cin >> command;//     // 选择进程//     int choice = rand() % slots.size();//     // 把任务给指定的进程//     sendAndWakeup(slots[choice].first, slots[choice].second, command);// }// else// {// }}// 关闭fd, 所有的子进程都会退出for (const auto &slot : slots){close(slot.second);}// 回收所有的子进程信息for (const auto &slot : slots){waitpid(slot.first, nullptr, 0);}
}

2、5、3 demo 代码解释

    给定的代码是一个C++程序,它创建多个子进程,并使用管道进行进程间通信来分配任务。让我们逐步了解代码:

  1. `waitCommand` 函数用于等待通过文件描述符(`waitFd`)发送的命令。它从文件描述符中读取一个无符号32位整数,并返回命令值。如果读操作返回0,意味着另一端已关闭连接,将设置 `quit` 标志为 true,退出程序。
  2. `sendAndWakeup` 函数负责通过文件描述符(`fd`)向由进程ID(`who`)标识的进程发送命令。它将命令值写入文件描述符,并显示指示正在执行的进程和命令的消息。
  3. 在 `main` 函数中,调用了 `load` 函数,该函数加载程序所需的一些必要数据(上述代码就是加在加载我们所需要的任务)。
  4. 创建了一个名为 `slots` 的pair vector,用于存储子进程的进程ID和文件描述符。
  5. 程序进入循环以创建多个子进程。在每次迭代中,使用 `pipe` 函数创建一个管道,提供一个数组 `pipefd` 以保存读端和写端的文件描述符。然后调用 `fork` 函数来创建一个子进程,并通过检查返回的ID是否为0来区分子进程。在子进程中,关闭管道的写端,并进入一个无限循环以等待命令并执行它们。如果收到无效命令,显示一条消息。最后,子进程退出。
  6. 在父进程中,关闭管道的读端,并将进程ID和写端文件描述符添加到 `slots` 向量中。
  7. 父进程进入另一个循环来分配任务给子进程。在每次迭代中,使用 `rand` 函数生成从0到 `handlerSize()` 的随机命令索引。然后,从 `slots` 向量中选择一个随机子进程,使用 `sendAndWakeup` 函数将选择的命令发送给该进程。发送命令后,程序暂停1秒钟,然后进行下一次迭代。
  8. 上述循环不断地将任务分配给子进程,确保它们执行各自的任务。还有被注释掉的代码,可以手动选择要发送给子进程的命令,但目前未激活。

  总之,此代码使用管道进行进程间通信创建多个子进程,并随机将任务分配给这些子进程。子进程不断等待命令并执行它们,而父进程则选择随机命令并将其发送给一个子进程。

三、命名管道

3、1 什么是命名管道

  命名管道(Named Pipe),也被称为FIFO(First In, First Out)管道,是一种特殊类型的文件,用于实现不同进程之间的通信。它允许两个或多个进程在系统中通过一个公共的命名管道进行双向通信

  命名管道有一个在文件系统中可见的唯一名称,而匿名管道没有。在创建命名管道时,需要指定一个路径和名称;而创建匿名管道时,不需要提供名称。

  我们在Linux下进行测试,使用命令进行创建一个命名管道。具体如下:

  1. 首先,我们需要使用系统调用函数mkfifo来创建一个命名管道。通过指定一个路径和一个唯一的名称,可以创建一个新的命名管道文件。指令如下:
    //mkfifo 路径+文件名称mkfifo ./mypipe

      创建的结果如下图所示:

  2. 接下来我们再打开命名管道。当然我们可以使用代码进行打开管道(下文会对此讲解),也可以直接向管道输入内容。具体如下图:

  通过上述情况,我们发现当向管道输入内容后,相当于就是打开了管道的写端。这是我们必须打开管道的读端进行读取。否则写端就会认为该操作(进程)一直在运行,并未结束。

3、2 命名管道通信

  命名管道的通信与匿名管道的通信基本相同。都需要看到同一块资源。但是匿名管道并不需要在磁盘上建立文件。命名管道是有一个唯一的路径和名称的。那进行通信的时候,会不会要进行磁盘I/O呢?命名管道通信会不会效率很慢呢?我们接着往下看。

  我们之前学习中,C语言模拟实现进度条小程序中提到了输出缓冲区。 同时在上述的匿名管道的demo代码中,也定义了我们自己的缓冲区(buffer[ ])。当然,因为这时的磁盘文件时用来通信的。是一个进程的数据让另一个进程看到,并不需要把数据刷新到磁盘。数据传输是通过内存缓冲区完成的,进程之间直接交换数据,不需要写入硬盘。命名管道的数据传输缓冲区位于内存中。当进程向命名管道写入数据时,数据会被暂存在内存缓冲区中接收进程从该缓冲区中读取数据。因此实际上并不会效率很低。

3、3 命名管道 demo 代码

3、3、1 mkfifo

  我们了解了利用指令创建命名管道。那么我们想通过接口来创建命名管道就可以使用mkfifo函数。具体使用如下图:

  我们看一下mkfifo的具体使用方法。函数原型:

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

  这个函数接受两个参数,pathname表示要创建的管道的路径名,mode表示设置文件权限的模式。下面我们看一个举例的例子:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>int main() {umask(0); // 设置文件权限掩码为0,确保创建的管道拥有指定的权限int result = mkfifo("/tmp/myfifo", 0666);if (result == 0) {printf("命名管道创建成功\n");} else {perror("命名管道创建失败");}return 0;
}

  具体运行结果如下:

   然后,另一个进程就可以打开此文件进行通信了!命名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。下面我们来模拟一下命名管道通信的方式。

3、3、2 demo 代码

 

// comm.hpp
#pragam once
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <ctime>
#include "Log.hpp"using namespace std;#define MODE 0666
#define SIZE 128string ipcPath = "./fifo.ipc";// Log.hpp
#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3const std::string msg[] = {"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}// client.cpp
#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;
}//server.cpp
#include "comm.hpp"static void getMessage(int fd)
{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;}}
}int main()
{// 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;int nums = 3;for (int i = 0; i < nums; i++){pid_t id = fork();if (id == 0){// 3. 编写正常的通信代码了getMessage(fd);exit(1);}}for(int i = 0; i < nums; i++){waitpid(-1, nullptr, 0);}// 4. 关闭文件close(fd);Log("关闭管道文件成功", Debug) << " step 3" << endl;unlink(ipcPath.c_str()); // 通信完毕,就删除文件Log("删除管道文件成功", Debug) << " step 4" << endl;return 0;
}

  这段代码是一个简单的进程间通信(IPC)示例,使用命名管道(FIFO)实现。下面对代码进行详细解释:

comm.hpp:

  • 定义了ipcPath字符串变量,表示管道文件的路径。
  • 定义了MODE和SIZE常量,分别表示管道文件的权限和缓冲区的大小。

Log.hpp:

  • 定义了Debug、Notice、Warning和Error四个级别的日志常量。
  • 定义了msg数组,存储了每个级别的日志名称。
  • 实现了Log函数,用于打印日志信息。

client.cpp:

  • 主函数中首先打开管道文件(以只写方式)。
  • 然后通过循环从标准输入读取用户输入的消息,并将消息写入管道中。

server.cpp:

  • 首先通过mkfifo函数创建一个管道文件。
  • 然后打开管道文件(以只读方式)。
  • 创建了3个子进程,每个子进程通过getMessage函数读取管道文件中的消息,并打印到标准输出。
  • 父进程通过waitpid函数等待所有子进程退出。
  • 最后关闭管道文件,删除管道文件。

  该代码演示了父进程和多个子进程之间通过命名管道进行通信的过程。父进程创建了一个管道文件,并打开该文件以便读取子进程写入的消息,同时创建多个子进程来同时处理不同的客户请求。每个子进程通过读取管道文件中的消息来获取客户端发送的数据,并打印到标准输出。父进程等待所有子进程退出后,关闭管道文件并删除管道文件。这样就完成了进程间的通信。

四、总结

  命名管道(named pipe)和匿名管道(anonymous pipe)是用于进程间通信的机制,它们在某些方面相同但也有一些区别。

相同点:

  1. 用途:两种管道都可以用于进程间的通信。进程可以通过管道在同一台计算机上进行数据交换。
  2. 工作原理:管道都提供了一个单向的、先进先出(FIFO)的数据流。进程通过写入端将数据写入管道,在读取端从管道中读取数据。
  3. 实现方式:管道通常由操作系统提供支持,通过在内核中创建一个缓冲区来实现。

区别:

  1. 命名管道有一个在文件系统中可见的唯一名称,而匿名管道没有。在创建命名管道时,需要指定一个路径和名称;而创建匿名管道时,不需要提供名称。
  2. 命名管道可以允许无关的进程之间进行通信,而匿名管道通常只用于相关的父子进程间通信。
  3. 命名管道使用文件系统的权限和属性,可以像普通文件一样进行操作(如权限控制、查看文件大小等),而匿名管道没有关联的文件系统属性。
  4. 命名管道可以由多个进程同时读取或写入,而匿名管道只能由在创建时有亲属关系的进程之间进行通信。

  本篇文章的内容就将接到这里,下篇内容会讲解到共享内存(System V)和信号量。感谢阅读。

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

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

相关文章

【OpenCV入门】第七部分——图像的几何变换

文章结构 缩放dsize参数实现缩放fx参数和fy参数实现缩放 翻转仿射变换平移旋转倾斜 透视cmath模块 缩放 通过resize()方法可以随意更改图像的大小比例&#xff1a; dst cv2.resize(src, dsize, fx, fy, interpolation)src&#xff1a; 原始图像dsize&#xff1a; 输出图像的…

华为云云服务器评测|云耀云服务器L实例快速部署MySQL使用指南

文章目录 前言云耀云服务器L实例介绍什么是云耀云服务器L实例&#xff1f;产品优势智能不卡顿价优随心用上手更简单管理更省心 快速购买查看优惠卷购买 安装MySQL重置密码安装更新apt的软件源列表安装MySQL 设置用户名、密码、权限配置安全组 总结 前言 哈喽大家好&#xff0c…

设置Linux CentOS7桥接模式连网

在虚拟机上安装centos7系统后&#xff0c;首要任务就是设置网络。 我们在文章《设置linux centos7连接网络》中讨论了如何设置NAT模式连网。本文讨论如何在设置好NAT模式后&#xff0c;调换为桥接模式。 仍采用图形化方式设置方法。 一、查看物理机网络 把虚拟机设置为桥接…

Doris workload group实战

1.创建测试用户&#xff1a;创建一个用户名为test&#xff0c;密码为test 的用户&#xff1a; create user test% IDENTIFIED BY test;给测试用户赋权&#xff1a;给用户test赋予数据库test.* 权限 grant SELECT_PRIV,LOAD_PRIV,CREATE_PRIV,ALTER_PRIV ON test.* TO test;开…

信息系统概述-生命周期-开发方法

信息系统概述-生命周期 考点分析信息系统概述信息系统分类企业目前所使用的具体的信息化系统信息系统的生命周期&#xff08;重要&#xff09;信息系统的开发方法&#xff08;重要&#xff09; 考点分析 每年都会考3分&#xff0c;2分会超纲 信息系统概述 信息系统分类 业务处理…

时间序列分析:掌握平稳性的概念

摄影&#xff1a;Chris Lawton on Unsplash 一、说明 平稳性是时间序列问题中的一个关键概念。它是指统计属性&#xff08;如均值、方差和协方差&#xff09;随时间变化的稳定性。为了建立有效的预测模型并确定时间序列数据中有意义的模式&#xff0c;了解平稳性的概念以及它与…

Spring MVC 之MVC 体系结构、什么是SpringMVC

Spring MVC简介 MVC 体系结构三层架构MVC设计模式 Spring MVC 是什么&#xff1f;扩展知识Spring模块Data Access/Integration&#xff08;数据访问/集成&#xff09;Web&#xff08;网络层&#xff09;AOP&#xff08;面向切面&#xff09;Messaging&#xff08;消息传送&…

文字验证码:简单有效的账号安全守卫!

前言 文字验证码不仅是一种简单易懂的验证方式&#xff0c;同时也是保护您的账号安全的重要工具。通过输入正确的文字组合&#xff0c;您可以有效地确认自己的身份&#xff0c;确保只有真正的用户才能访问您的账号。 HTML代码 <script src"https://cdn6.kgcaptcha.…

java八股文面试[数据库]——Page页的结构

mysql中数据是存储在物理磁盘上的&#xff0c;而真正的数据处理又是在内存中执行的。由于磁盘的读写速度非常慢&#xff0c;如果每次操作都对磁盘进行频繁读写的话&#xff0c;那么性能一定非常差。为了上述问题&#xff0c;InnoDB将数据划分为若干页&#xff0c;以页作为磁盘与…

算法通关村第十九关——最小路径和

LeetCode64. 给定一个包含非负整数的 m n 网格 grid,请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 输入&#xff1a;grid[[1,3,1],[1,5,1],[4,2,1]] 输出&#xff1a;7 解释&#xff1a;因为路径1→3→1→1→1的总和最小。 public int minPath…

Qt鼠标点击事件处理:按Escape键退出程序

创建项目 Qt 入门实战教程&#xff08;目录&#xff09; 首先&#xff0c;创建一个名称为QtKeyEscape的Qt默认的窗口程序。 参考 &#xff1a;Qt Creator 创建 Qt 默认窗口程序 Qt响应键盘Escape事件 打开Qt Creator >>编辑 >> 项目 >> Headers>> …

【iVX】iVX的低代码未来发展趋势:加速应用开发的创新之路

简介&#xff1a; 随着数字化转型的飞速发展&#xff0c;企业和组织对快速开发和交付高质量应用的需求越来越迫切。低代码开发平台作为一种创新的解决方案&#xff0c;极大地简化了应用程序的开发过程。在这一领域&#xff0c;iVX低代码平台作为领先的创业公司&#xff0c;正在…

Python小知识 - Python爬虫进阶:如何克服反爬虫技术

Python爬虫进阶&#xff1a;如何克服反爬虫技术 爬虫是一种按照一定的规则&#xff0c;自动抓取网页信息的程序。爬虫也叫网页蜘蛛、蚂蚁、小水滴&#xff0c;是一种基于特定算法的自动化程序&#xff0c;能够按照一定的规则自动的抓取网页中的信息。爬虫程序的主要作用就是从一…

记录 使用 git 克隆仓库报错:Warning: Permanently added‘github.com’ to the .....(ssh )

解决方法&#xff1a; 1. 新建空文件夹->右键->点击 Git Bash Here2. 输入 cd C&#xff1a;3. 输入 cat ~/.ssh/id_rsa.pub4. 输入 ssh-keygen重复回车&#xff0c;生成一个矩形&#xff0c;则说明公钥已经生成了。重复步骤3&#xff0c;生成publickey&#xff0c;右键…

Vue2基础学习

vue基础学习 Vue基础指令v-show 和 v-ifv-on指令v-bind指令v-bind操作classv-bind 操作stylev-for 指令练习&#xff1a;图书管理案例v-modelv-model原理 指令修饰符v-model指令修饰符click.stop-》阻止冒泡按键修饰符阻止默认行为 计算属性计算属性简写computed计算属性VS方法…

HTML 标签讲解

HTML 标签讲解 HTML 语言结构根元素元数据元素主体根元素大纲元素文本内容语义化内联文本图像与多媒体编辑标识table表格内容表单内容table表单 HTML 语言结构 Markup &#xff08;标记、标签&#xff09;用来容纳和描述内容 严格意义上&#xff0c;标签是指开始标签&#xf…

优化爬虫效率:利用HTTP代理进行并发请求

网络爬虫作为一种自动化数据采集工具&#xff0c;广泛应用于数据挖掘、信息监测等领域。然而&#xff0c;随着互联网的发展和网站的增多&#xff0c;单个爬虫往往无法满足大规模数据采集的需求。为了提高爬虫的效率和性能&#xff0c;我们需要寻找优化方法。本文将介绍一种利用…

企业无线局域网部署最佳实践

文章目录 企业无线局域网部署最佳实践引言1. 无线网规划和设计a. 选择合适的频宽b. 网络规划工具c. 考虑物理环境d. 用户密度和需求e. 未来扩展f. 安全性和策略g. 测试和验证2. 无线局域网容量2.1 用户和设备预测2.2 应用流量分析2.3 带宽管理2.4 无线技术选择2.5 网络健康检查…

鳄鱼指标和ADX组合后,发现买卖信号真清晰

通过之前的文章分享&#xff0c;anzo capital昂首资本相信各位投资者对ADX已经有了深刻的理解&#xff0c;今天在后台有小伙伴分享了鳄鱼指标&#xff0c;没想到的是&#xff0c;鳄鱼指标和ADX组合后&#xff0c;买卖信号变的更清晰了&#xff0c;今天就分享一下。 鳄鱼指标是一…

uni-app 之 vue语法

uni-app 之 vue语法 image.png --- v-html 字符 --- image.png <template><view><view>{{title}}</view>--- v-html 字符 ---<view>{{title2}}</view><view v-html"title2"></view><view>{{arr}}</view&g…