零基础Linux_17(进程间通信)VSCode环境安装+进程间通信介绍+pipe管道mkfifo

news/2024/5/20 14:16:58/文章来源:https://blog.csdn.net/GRrtx/article/details/132240817

目录

1. VSCode环境安装

1.1 使用VSCode

1.2 远程链接到Linux机器

1.3 VSCode调试

2. 进程间通讯介绍

2.1 进程间通讯的概念和意义

2.2 进程间通讯的策略和本质

3. 管道

3.1 管道介绍

3.2 匿名管道介绍

3.3 匿名管道示例代码

3.3.1 建立管道的pipe

3.3.2 匿名管道完整代码

3.3.3 匿名管道模拟进程池

3.4 命名管道介绍

3.4.1 命名管道概念

3.4.2 mkfifo和unlink小实验

3.5 命名管道示例代码

3.5.2 命名管道示例完整代码:

4. 笔试选择题:

答案及解析

本篇完。


1. VSCode环境安装

下载可以在官网直接下载,慢的话可以直接复制这个链接:

https://vscode.cdn.azure.cn/stable/30d9c6cd9483b2cc586687151bcbcd635f373630/VSCodeUserSetup-x64-1.68.1.exe

安装很简单,可以自己安装一下,不行就搜一搜。

1.1 使用VSCode

VSCode至少是一个编辑器,和Vim一样。

刚打开应该是英文的,点击左边应用搜索chinese,直接安装简体中文就行。

在桌面创建一个空文件夹,然后在VSCode中点击打开文件夹,选择桌面,选择刚创建的文件夹打开就行。然后这里选择新建文件:

这里新建test.c文件,此时右下角可能会有安装C/C++的插件等,安装就行。

(左下角设置可以修改字体大小)写一段代码,Ctrl s保存:

再新建一个test.cpp文件,写一段代码并Ctrl s保存,然后顺便创建一个文件夹试试:

 也可以创建其它语言的文件,此时桌面刚才新建的文件夹就有了在VSCode创建的东西:

运行是运行不了的,这里远程链接我们前面使用的Linux机器(也可以用其它方法)

1.2 远程链接到Linux机器

在应用里搜索Remote并安装,滑下来你可以看到步骤:

这里按F1键,选中Remote-SSH Add,输入ssh 你的用户名@你的公网IP(xshell登录可以看)

 

回车后按第一行就配置好了,这里可以关掉重新启动VSCode,然后左边的远程资源管理器就出现了你刚才链接的机器。

这里右键机器,然后选择上面一行的在当前窗口链接,然后选择Linux,输入刚才用户名密码

 然后这里打钩了就是链接好了:

这里打开xshll,在平时写代码的路径创建一个TestVSCode目录:

 点击左上角的资源管理器,新建文件夹,默认就是我们在xshell的路径:

 选择TestVSCode目录,确定,选择Linux,然后输密码就行,

这里连接好了,新建一个test.cpp文件,写点代码,Ctrl S保存:

 此时在xshell就自动同步我们的文件和代码了,编译运行试试:

你也可以在VSCode下 Ctrl ~ 调出终端输入命令:

 

 成功成功。

1.3 VSCode调试

C++相关的插件刚才应该也一带安装了,没安装的这里打钩都能安装了:

 这里安装下GDB Debug,然后在左边类似小爬虫的图标上点击这里:

然后选择GDB Debug调试器:

这个小方括号里面的内容都可以删掉了,然后点击左边的添加配置,选择第一行什么gdb 启动:

然后只需要改这一行:"program": "输入程序名称,例如 ${workspaceFolder}/a.out",

改成:"program": "${workspaceFolder}/test_gdb",

Ctrl 保存,在终端生成test_gdb调试文件,运行:

 然后在点小爬虫左上角的开始调试(F5)就行,快捷键都和VS2022差不多的:

断点也能直接打,左边变量啥的都能看到,但是VSCode的调试有时会很卡,所以不常用,

据此,VSCode的简单使用就讲完了。

2. 进程间通讯介绍

IPC(Inter-Process Communication,进程间通讯)

2.1 进程间通讯的概念和意义

什么是进程间通信?:

进程间通信是两个或者多个进程之间进行通信,行为如下:

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

为什么要进行通信?:

我们在之前讲过 "进程之间是具有独立性" 的,如果进程间想交互数据,成本会非常高。

因为独立性之本质即 "封闭",进程们你封闭你的我封闭我的,那么进程间的交流可谓是窒碍难行。

  • 进程间的通信说白了就是 "数据交互",在操作系统中,进程是独立运行的程序,它们之间需要相互协作完成任务。进程间通信的目的是为了实现进程之间的数据共享、协作和同步,从而提高系统的效率和可靠性。

很多场景下我们需要多进程进行协同处理一件事情。

不要以为,进程独立了就是彻底独立,有时我们需要双方能够进行一定程序的信息交互。

2.2 进程间通讯的策略和本质

如何进行进程通信?:

当前主要是通过三种策略来实现进程间通信的,每一种策略下都有很多种通信方式:

① 管道:通过文件系统通信。
匿名管道
命名管道


② System Ⅴ:聚焦在本地通信。
共享内存
消息队列
信号量


③ POSIX:让通信可以跨主机。
共享内存
消息队列
信号量
互斥量
条件变量
读写锁

进程通信的本质是什么?:

我们知道,进程是相互独立的,所以进程之间的通信成本肯定不低。

为了进程在通信的时候,既能满足进程之间的独立性,又能够到达通信的目的,那么进程之间通信的地点就不能在两个进程中。

一个进程将自己的数据交给另一个进程,并且还要等待另一个进程的应答,这样一来,这个进程将不独立了,受到了另一个进程的影响,与进程的独立性矛盾。
所以,两个进程进行通信的地点必须是由第三方提供的,第三方只能是操作系统。操作系统提供的这个地点被我们称为:公共资源。

公共资源有了,还必须让要通信的进程都看到这一份公共资源,此时要通信的进程将有了通信的前提。之后就是进程通信,也就是访问这块公共资源的数据。

之所以有不同的通信方式,是因为公共资源的种类不一,如果公共资源是一块内存,那么通信方式就叫做共享内存,如果公共资源是一个文件,也就是struct file结构体,那么就叫做管道。

3. 管道

3.1 管道介绍

什么是管道?管道是 Unix 系统中最古老的 IPC 形式,

一个进程连接到另一个进程的数据流称为管道 (Pipe)。

我们来回忆一下文件系统:

父进程打开一个文件,操作系统在内存上创建一个struct file结构体对象,里面包含文件的各种属性,以及对磁盘文件的操作方法。每个struct file对象中还有一个内核缓冲区,这个缓冲区中可以存放数据。当子进程创建的时候,父进程的文件描述符表会被子进程继承下去,所以此时子进程在相同的fd处也指向父进程打开的文件。文件描述符表一个进程维护一个,但是struct file结构体对象在内存中只有一个,由操作系统维护。此时,父子进程将看到了同一份公共资源,也就是操作系统在内存中维护的struct file对象,并且父子进程也都和这份资源建立了连接。此时父子进程通信的基础有了,它们就可以通信了。

父进程向文件中写内容,写完后继续干自己的事,并不破坏父进程的独立性。
子进程向文件中读内容,读完后继续干自己的事,并不破坏子进程的独立性。

这样一读一写,父子进程将完成了一次进程间通信。

而我们又知道,对文件进行IO操作时,由于需要访问硬盘,所以速度非常的慢,而且我们发现,父子间进行通信,磁盘中文件的内容并不重要,重要的是父进程写了什么,子进程又读到了什么。

此时操作系统为了提高效率,就关闭了内存中struct file和硬盘中文件进行IO的通道。
父进程写数据写到了struct file的内核缓冲区中。
子进程读数据从struct file的内核缓冲区中读取。
此时,父子间通信仍然正常进行,并且效率还非常的高,而且还没有影响进程的独立性。而这种不进行IO的文件叫做内存级文件。

这种由文件系统提供公共资源的进程间通信,就叫做管道。

两个进程就通过管道建立起了连接,并且可以进程进程之间的通信。而管道又分为匿名管道和命名管道。

3.2 匿名管道介绍

  • 匿名管道:顾名思义,就是没有名字的文件(struct file)。
  • 匿名管道只能用于父子进程间通信,或者由一个父进程创建的兄弟进程之间进行通信。

现在我们知道了匿名管道就是没有名字的文件,通过管道进行通信时,只需要通信双方打开同一个文件就可以。

我们通过系统调用open打开文件的时候,会指定打开方式,是读还是写。

当父进程以写方式打开一个文件的时候,创建的子进程会继承父进程的一切。
此时子进程也是以写的方式打开的这个文件。
既然是通信,势必有一方在写,一方在读,而现在父子双方都是以写的方式打开,它们怎么进行通信呢?:父进程以读和写的方式打开同一份文件两次。

这样一来,创建子进程后,父子进程都可以对管道进行读和写,它们就可以进行通信了,上面的问题就解决了。之所以命名为管道,那么就有和管道类似的性质。在生活中,我们对水管,它的流向只能是单向的,管道也一样,通过管道建立的通信只能进行单向数据通信

如上图,假设父进程对管道写,子进程对管道读。

  • 为了防止父进程对管道进行误读,以及子进程对管道进行误写,破坏通信规则。
  • 将父进程的读端关闭,将子进程的写端关闭,使用系统调用close(fd)。

此时,父子进程之间的单向数据通信就建立起来了,下一步就可以进行通信了。如果想进行双向通信,可以建立两个管道。

3.3 匿名管道示例代码

示例:从键盘读取数据,写入管道,读取管道,写到屏幕。

这里在rtx2目录下新建一个linux_17目录,在里面新建一个pipe目录,在VSCode写代码:

前面弄好了VSCode(没弄好用Vim写也行),打开VSCode打开上面的pipe文件夹,链接,输普通用户的密码,写个Makefile:

3.3.1 建立管道的pipe

上面都是理论上的,具体到代码中是如何建立管道的呢?

既然是操作系统中的文件系统提供的公共资源,当然是用系统调用来建立管道了。

 原型 int pipe(int pipefd[2]);

#include <unistd.h>

功能:创建一个匿名管道

形参:int pipefd[2]是一个输出型参数,一个数组,该数组只有两个元素,下标分别为0和1。

下标为0的元素表示的是管道读端的文件描述符fd。

下标为1的元素表示的是管道写端的文件描述符fd。

这里巧记:pipefd[0(嘴巴,读书)]: 读端 , pipefd[1(钢笔,写)]: 写端

返回值:返回0,管道创建成功。返回-1,管道创建失败,并将错误码自动写入errno中。

使用系统调用pipe,直接就会得到两个fd,并且放入父进程的文件描述符表中,不用打开内存级文件两次。那么,父进创建管道以后,得到的两个文件描述符是多少呢?根据前面所学,是3和4吗?我们代码中来看,mypipe.cpp:

#include <iostream>
#include <cerrno> // C++包C语言头文件常用的方法,和.h效果一样
#include <cstring>
#include <unistd.h> // pipeusing namespace std;int main()
{int pipefd[2];int ret = pipe(pipefd); // 一.创建管道if(ret < 0){cerr << errno << ": " << strerror(errno) << endl;}cout << "pipefd[0]: " << pipefd[0] << endl; // 3cout << "pipefd[1]: " << pipefd[1] << endl; // 4return 0;
}

可以看到,创建管道后返回的两个fd值,果然是3和4,因为0,1,2分别被stdin,stdout,stderr占用。知道了如何使用系统调用创建管道以后,接下来就创建子进程,然后关闭不需要的端口了,原理已经清楚,直接看代码。

#include <iostream>
#include <cerrno> // C++包C语言头文件常用的方法,和.h效果一样
#include <cstring>
#include <cassert>
#include <unistd.h> // pipe + closeusing namespace std;int main()
{int pipefd[2];int ret = pipe(pipefd); // 一.创建管道if(ret < 0){cerr << errno << ": " << strerror(errno) << endl;}// cout << "pipefd[0]: " << pipefd[0] << endl; // 3// cout << "pipefd[1]: " << pipefd[1] << endl; // 4pid_t id = fork(); // 二.创建子进程assert(id != -1);if (id == 0) // 子进程,读,关闭写{close(pipefd[1]);// 通信close(pipefd[0]);exit(0);}close(pipefd[0]); // 父进程,写,关闭读// 通信close(pipefd[1]);return 0;
}

此时在代码层面上, 父子双方就已经建立了连接了,接下来就是通信数据了。

子进程读,代码:

父进程写,代码:

3.3.2 匿名管道完整代码

#include <iostream>
#include <cerrno> // C++包C语言头文件常用的方法,和.h效果一样
#include <cstring>
#include <cassert>
#include <unistd.h> // pipe + close + read + write
#include <sys/types.h> // waitpid两个头文件
#include <sys/wait.h>using namespace std;int main()
{int pipefd[2];int ret = pipe(pipefd); // 一.创建管道if(ret < 0){cerr << errno << ": " << strerror(errno) << endl;}// cout << "pipefd[0]: " << pipefd[0] << endl; // 3// cout << "pipefd[1]: " << pipefd[1] << endl; // 4pid_t id = fork(); // 二.创建子进程assert(id != -1);if (id == 0) // 子进程,读,关闭写{close(pipefd[1]);// 三. 子进程读char buffer[1024 * 8];while (true){// sleep(10);ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1); // read读if (s > 0) // 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等{buffer[s] = 0;cout << "child get a message[" << getpid() << "] Father# " << buffer << endl;}else if (s == 0) // 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾{cout << "writer quit(father), me quit" << endl;break;}}close(pipefd[0]);exit(0);}close(pipefd[0]); // 父进程,写,关闭读// 四. 父进程写string message = "我是父进程,我正在给你发消息";int count = 0;char send_buffer[1024 * 8];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);cout << count << endl;if (count == 5) {cout << "writer quit(father)" << endl;break;}}close(pipefd[1]);pid_t ret_id = waitpid(id, nullptr, 0);cout << "id : " << id << " ret_id: " << ret << endl;assert(ret_id > 0); // 断言只在debug起效(void)ret_id; // 只是证明ret_id被使用过return 0;
}

这就实现了简单的匿名管道。

可以自己改代码试一试管道的读取特征:

场景特征
读取慢,写入快写入端阻塞在write处
读取快,写入慢读取端阻塞在read处
读取端关闭操作系统终结写端
写入端关闭读取端read返回0

管道之所以有这样的读取特征,其实是为了对管道中的数据进行保护,这种方式称为互斥,后面会详细讲解这一概念。

匿名管道本身也有它自己的特征,如下:

  • 管道的生命周期随进程的结束而结束,当所有进程都关闭该管道的文件描述符时,管道被销毁。
  • 管道可以用来进行具有血缘关系的进程之间进行通信,常用于父子进程通信。
  • 管道是半双工的通信方式(单向通信)
  • 管道是面向字节流的(在网络部分讲解)。
  • 管道有互斥与同步机制对共享资源进行保护(以后讲解)。

3.3.3 匿名管道模拟进程池

这里在linux_17目录下创建ProcessPool目录,在VSCode打开,在里面写代码,

直接放匿名管道模拟进程池的代码了,可以自己跟着注释读一遍,也可以跟着写一写:

Makefile:

ProcessPool:ProcessPool.cppg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f ProcessPool

ProcessPool.cpp:

#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp" // 发任务的文件,.hpp -> .h和.cpp写一起#define PROCESS_NUM 5 // 创建的子进程数目using namespace std;int waitCommand(int waitFd, bool& quit) //如果对方不发,我们就阻塞
{uint32_t command = 0; // uint32_t四个字节ssize_t s = read(waitFd, &command, sizeof(command)); // 期望读取四个字节if (s == 0) // 读到0让子进程退出{quit = true;return -1;}assert(s == sizeof(uint32_t)); // 不是四个字节就报错return command;
}void sendAndWakeup(pid_t who, int fd, uint32_t command)   // 通过文件描述符,向哪一个文件发什么命令
{               // who给哪个进程,这个进程的idwrite(fd, &command, sizeof(command));cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
}int main()
{// 代码中关于fd的处理,有一个小问题,不影响我们使用,但是你能找到吗??load();vector<pair<pid_t, int>> slots; // 存放子进程pid和子进程写端id(pipefd)vector<int> deleteFd; // 存放要删除的子进程写端fd(不删除也不会出问题)for (int i = 0; i < PROCESS_NUM; i++) // 先创建多个进程{int pipefd[2] = { 0 };int ret = pipe(pipefd); // 创建管道assert(ret == 0); // 等于0才创建成功(void)ret;pid_t id = fork();assert(id != -1);if (id == 0) // 子进程,进行读取{close(pipefd[1]); // 关闭写端for (int i = 0; i < deleteFd.size(); i++) // 关闭所以继承下来的写端fd{close(deleteFd[i]);}while (true){// 等命令bool quit = false; // 默认不退出int command = waitCommand(pipefd[0], quit); // 如果对方不发,我们就阻塞if (quit) // 读到0就退出关闭所有进程{break;}if (command >= 0 && command < handlerSize()) // 执行对应的命令{                                            // handlerSize任务方法的个数callbacks[command]();}else{cout << "非法command: " << command << endl;}}exit(1);}close(pipefd[0]); // 父进程,进行写入,关闭读端slots.push_back(pair<pid_t, int>(id, pipefd[1])); // 把此次循环得到的子进程id和子进程写端的id保存deleteFd.push_back(pipefd[1]); // 把要被继承下去的子进程写端fd保存起来}// 父进程均衡地派发任务(单机版的负载均衡)srand((unsigned long)time(nullptr) ^ getpid() ^ 2335643123L); // 仅仅让数据源更随机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 // 选择错误//{//}}for (const auto& slot : slots) // 关闭fd, 所有的子进程都会退出{close(slot.second);}for (const auto& slot : slots) // 回收所有的子进程信息{waitpid(slot.first, nullptr, 0);}
}

Task.hpp:

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <unistd.h>
#include <functional>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; // \t制表符}
}int handlerSize() // 直接返回有多少个任务的方法
{return callbacks.size();
}

编译运行:

3.4 命名管道介绍

3.4.1 命名管道概念

命名管道:顾名思义,有名字的管道(内存级文件)。

根据前面的学习,我们知道,父子进程间使用匿名管道的方式进行通信,是通过子进程继承父进程的方式来实现,而且匿名匿名管道常用于父子进程直接,或者由血缘关系的进程直接。

那么,如果两个进程毫无关系呢?此时就不能继承了,那这两个进程如何建立连接呢?

还是采用管道的方式,但是这个管道是有名字的管道,这样一来,两个进程就可以打开同一个管道文件建立连接。

还是这张图,此时内存中的struct file在磁盘上有对应文件的,如上图中的fifo.ipc文件。

3.4.2 mkfifo和unlink小实验

创建命名管道指令,man mkfifo:

  • 指令:mkfifo 文件名
  • 功能:创建命名管道文件

FIFO(first in first out)因为管道是单向通信的,这里在linux_17目录下创建fifo目录,进入并建立一个命名管道:

此时就成功地建立了一个命名管道,可以发现它的(文件类型)权限前面的字母是p(pipe),而目录的文件类型是d(directory)。命名管道文件类型是p,而且该文件还有inode,说明在磁盘上是真实存在的。

当磁盘中有了命名管道文件以后,两个进程将可以通过这个管道文件进行通信了,步骤和匿名管道非常相似。一个进程以写方式打开管道文件,另一个进程以读端方式打开管道文件。此时两个进程将建立了连接,然后将可以进行通信了。我们知道,进程间通信的前提是,要通信的进程能够看到同一份公共资源,那么命名管道是如何做到这一点的呢?

让不同的进程打开指定路径下同一个管道文件。

往name_pipe写点东西:

此时发现类似堵塞住了? 此时处于的就是阻塞状态,它需要被另一个进程读取:

此时就相当于完成了两个进程之间的通讯。

你可以通过unlink或者rm删掉命名管道(效果是差不多的),man unlink

3.5 命名管道示例代码

可以在shell中通过命令的方式创建管道文件,两个进程直接去使用它。也可以像文件一样,在进程中创建管道文件,此时就需要用到系统调用。man 3 mkfifo:

  • 第一个形参:管道文件的名字
  • 第二个形参:创建管道文件的权限
  • 返回值:0表示创建成功,-1表示创建失败。

在进程中删除管道文件:man 2 unlink:

写个测试代码:

如果带上unlink:

此时命名管道在进程里打开,也在进程里关闭了。

3.5.2 命名管道示例完整代码:

在VSCode打开上面的fifo文件夹,在里面写代码,

Makefile:

.PHONY:all
all:client mutiServerclient:client.cppg++ -o $@ $^ -std=c++11
mutiServer:server.cppg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client mutiServer

comm.hpp:(一些头文件和宏和一个路径)

#ifndef _COMM_H_
#define _COMM_H_#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include "Log.hpp"using namespace std;#define MODE 0666
#define SIZE 128string ipcPath = "./fifo.ipc";#endif

Log.hpp:(打印日志,不加也行)

#ifndef _LOG_H_
#define _LOG_H_#include <iostream>
#include <ctime>#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;
}#endif

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 file{cerr << "[" << getpid() << "] " << "read end of file, clien quit, server quit too" << endl;break;}else // 读取失败{perror("read");break;}}
}int main()
{if (mkfifo(ipcPath.c_str(), MODE) != 0)// 1. 创建管道文件{perror("mkfifo");exit(1);}Log("创建管道文件成功", Debug) << " step 1" << endl;int fd = open(ipcPath.c_str(), O_RDONLY); // 2. 正常的文件操作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);}close(fd); // 4. 关闭文件Log("关闭管道文件成功", Debug) << " step 3" << endl;unlink(ipcPath.c_str()); // 通信完毕,就删除文件Log("删除管道文件成功", Debug) << " step 4" << endl;return 0;
}

client.cpp:(用户端)

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

make之后先运行服务端再运行客户端,就能实现类似微信发信息的功能(客户端Ctrl C关闭):

这样就实现了命名管道的通信方式,建议自己写一遍代码测试一下。

4. 笔试选择题:

1. 以下描述正确的有:

A.进程之间可以直接通过地址访问进行相互通信

B.进程之间不可以直接通过地址访问进行相互通信

C.所有的进程间通信都是通过内核中的缓冲区实现的

D.以上都是错误的

2. 以下选项属于进程间通信的是()[多选]

A.管道

B.套接字

C.内存

D.消息队列

3.  下列关于管道(Pipe)通信的叙述中,正确的是() 

A.一个管道可以实现双向数据传输

B.管道的容量仅受磁盘容量大小限制

C.进程对管道进行读操作和写操作都可能被阻塞

D.一个管道只能有一个读进程或一个写进程对其操作

4. 以下关于管道的描述中,正确的是 [多选]

A.匿名管道可以用于任意进程间通信

B.匿名管道只能用于具有亲缘关系的进程间通信

C.在创建子进程之后也可以通过创建匿名管道实现父子进程间通信

D.必须在创建子进程之前创建匿名管道才能实现父子进程间通信

5. 以下关于管道的描述中错误的是  [多选]

A.可以通过int pipe(int pipefd[2])接口创建匿名管道,其中pipefd[0]用于从管道中读取数据

B.可以通过int pipe(int pipefd[2])接口创建匿名管道,其中pipefd[0]用于向管道中写入数据

C.若在所有进程中将管道的写端关闭,则从管道中读取数据时会返回-1;

D.管道的本质是内核中的一块缓冲区;

6. 以下关于管道描述正确的有:

A.命名管道可以用于同一主机上的任意进程间通信

B.向命名管道中写入的数据越多,则管道文件越大

C.若以只读的方式打开命名管道时,则打开操作会报错

D.命名管道可以实现双向通信


7. 以下关于管道描述正确的有:

A.命名管道和匿名管道的区别在于命名管道是通过普通文件实现的

B.命名管道在磁盘空间足够的情况下可以持续写入数据

C.多个进程在通过管道通信时,删除管道文件则无法继续通信

D.命名管道的本质和匿名管道的本质相同都是内核中的一块缓冲区

答案及解析

1. B

A错误: 进程之间具有独立性,拥有自己的虚拟地址空间,因此无法通过各自的虚拟地址进行通信(A的地址经过B的页表映射不一定映射在什么位置)

B正确

C错误: 除了内核中的缓冲区之外还有文件以及网络通信的方式可以实现D

2. ABD                

典型进程间通信方式:管道,共享内存,消息队列,信号量。 除此之外还有网络通信,以及文件等多种方式

C选项,这里的内存太过宽泛,并没有特指某种技术,错误。

3. C

A.一个管道可以实现双向数据传输

B.管道的容量仅受磁盘容量大小限制

C.进程对管道进行读操作和写操作都可能被阻塞

D.一个管道只能有一个读进程或一个写进程对其操作

4. ABD

A.匿名管道可以用于任意进程间通信

B.匿名管道只能用于具有亲缘关系的进程间通信

C.在创建子进程之后也可以通过创建匿名管道实现父子进程间通信

D.必须在创建子进程之前创建匿名管道才能实现父子进程间通信

5. BC

  • 管道本质是内核中的一块缓冲区,多个进程通过访问同一块缓冲区实现通信。
  • 使用int pipe(int pipefd[2])接口创建匿名管道,pipefd[0]用于从管道读取数据,pipefd[1]用于向管道写入数据。
  • 管道特性:半双工通信,自带同步与互斥,生命周期随进程,提供字节流传输服务。
  • 在同步的提现中,若管道所有写段关闭,则从管道中读取完所有数据后,继续read会返回0,不再阻塞;若所有读端关闭,则继续write写入会触发异常导致进程退出

根据以上管道理解分析:A正确,B错误,C错误,D正确

6. A

  • 匿名管道只能用于具有亲缘关系的进程间通信,命名管道可用于同一主机上的任意进程间通信
  • 管道的通信本质是通过内核中一块缓冲区(内存)时间数据传输,而命名管道的管道文件只是一个标识符,用于让多个进程能够访问同一块缓冲区
  • 管道是半双工通信,是可以选择方向的单向通信
  • 命名管道打开特性为,若以只读方式打开文件,则会阻塞,直到管道被以写的方式打开,反之亦然

7. D

A错误, 管道的本质是内核中的缓冲区,命名管道文件是缓冲区的标识

B错误, 管道在缓冲区写满后会写阻塞,跟磁盘空间并无关系

C错误, 管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信

D正确

再就是自己实现匿名管道和命名管道。

本篇完。

下一篇:零基础Linux_18(进程间通信)共享内存+消息队列+信号量。

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

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

相关文章

Codeforces Round 903 (Div. 3) C(矩形旋转之后对应的坐标)

题目链接&#xff1a;Codeforces Round 903 (Div. 3) C 题目&#xff1a; 思想&#xff1a; 旋转之后对应的坐标&#xff1a; &#xff08;i&#xff0c;j&#xff09;&#xff08;n1-j&#xff0c;i&#xff09;&#xff08;n1-i&#xff0c;n1-j&#xff09;&#xff08;j…

13.SpringBoot项目之Service层

SpringBoot项目之Service层 JavaEE三层架构 为了项目维护方便&#xff0c;为了项目开发便利。三层架构功能控制器层&#xff1a;controller方便和前端数据进行交互业务层&#xff1a;service处理各种业务持久化层&#xff1a;mapper和数据库进行数据交互 抽取出service层 按…

【C++】哈希对unordered_map和unodered_set的封装

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; C学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我最大…

JOSEF约瑟 可调漏电继电器RT-LB230KS+Q-FL-100 导轨安装 配套零序互感器

一、产品用途及特点 RT-LB230KS漏电继电器&#xff08;以下简称继电器&#xff09;适用于交流电压为660V.至1140V电压系统中,频率为50Hz,电流15~4000A线路中做有无中性点漏电保护. 该继电器可与带分励脱扣器或失压脱扣器的断路器、交流接触器、磁力启动器等组成漏电保护装置&…

压缩炸弹,Java怎么防止

一、什么是压缩炸弹&#xff0c;会有什么危害 1.1 什么是压缩炸弹 压缩炸弹(ZIP)&#xff1a;一个压缩包只有几十KB&#xff0c;但是解压缩后有几十GB&#xff0c;甚至可以去到几百TB&#xff0c;直接撑爆硬盘&#xff0c;或者是在解压过程中CPU飙到100%造成服务器宕机。虽然…

谢邀,ADconf安全大会

儒道易行 道虽远&#xff0c;行则易至&#xff1b;儒虽难&#xff0c;坚为易成 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教&#xff0c;万分感谢。 免责声明&#xff1a;由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失&am…

C. JoyboardCodeforces Round 902

C. Joyboard 样例1列表找规律&#xff1a; #include<iostream> #define int long long using namespace std; signed main() {int T;cin>>T;while(T--){int n,m,k;cin>>n>>m>>k;if(k1){cout<<1<<endl;}else if(k2){cout<<m…

vscode 资源管理器移动到右边

目录 vscode 资源管理器移动到右边 vscode 资源管理器移动到右边 点击 文件》首选项》设置》工作台》外观》 找到这个配置下拉选择左右

竞赛选题 深度学习OCR中文识别 - opencv python

文章目录 0 前言1 课题背景2 实现效果3 文本区域检测网络-CTPN4 文本识别网络-CRNN5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习OCR中文识别系统 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;…

恢复Windows 11经典右键菜单:一条命令解决显示更多选项问题

恢复Windows 11经典右键菜单&#xff1a;一条命令解决显示更多选项问题 恢复Windows 11经典右键菜单&#xff1a;一条命令解决显示更多选项问题为什么改变&#xff1f;恢复经典右键菜单 我是将军我一直都在&#xff0c;。&#xff01; 恢复Windows 11经典右键菜单&#xff1a;一…

介绍一款小巧的Excel比对工具-DiffExcel

【缘起&#xff1a;此前找了一通&#xff0c;没有找到免费又好用的Excel比对工具&#xff0c;而ExcelBDD需要把Excel文件存放到Git&#xff0c;因此迫切需要Excel比对工具。 最新升级到V1.3.3&#xff0c;因为git diff有变化&#xff0c;原来是git diff会修改文件名&#xff0…

【重拾C语言】十二、C语言程序开发(穷举与试探——八皇后问题)

目录 前言 十二、C语言程序开发 12.1~3 自顶向下、逐步求精&#xff1b;结构化程序设计原则&#xff1b;程序风格 12.4 八皇后——穷举与试探 12.4.1 穷举法 示例&#xff1a;寻找一个整数的平方根 12.4.2 试探法 示例&#xff1a;计算给定数字的阶乘 12.4.3 穷举与试…

[论文笔记]SimCSE

引言 今天带来一篇当时引起轰动的论文SimCSE笔记,论文题目是 语句嵌入的简单对比学习。 SimCSE是一个简单的对比学习框架,它可以通过无监督和有监督的方式来训练。 对于无监督方式,输入一个句子然后在一个对比目标中预测它自己,仅需要标准的Dropout作为噪声。这种简单的…

IDEA 修改插件安装位置

不说假话&#xff0c;一定要看到最后&#xff0c;不然你以为我为什么要自己总结&#xff01;&#xff01;&#xff01; IDEA 修改插件安装位置 前言步骤 前言 IDEA 默认的配置文件均安装在C盘&#xff0c;使用时间长会生成很多文件&#xff0c;这些文件会占用挤兑C盘空间&…

vue打包配置

1.资源相对引用路径 build/webpack.prod.conf.js 找到output&#xff1a;增加 publicPath: ./,2.背景图片的引用问题 build/utils.js 找到if (options.extract) { 添加一行 publicPath:../../3.index.html页面没有显示内容 config/index.js 更改config/index.js 中的参数…

Zotero同步坚果云

实用教程 无意之中发现的Zotero同步坚果云的教程&#xff0c;简直和自己当时看视频&#xff0c;搜经验贴做的步骤一模一样&#xff0c;十分赞&#xff01;值得收藏&#xff01;只是忘记当时在哪保存的图片了&#xff0c;所以没法引用&#xff01;只能在这借花献佛&#xff0c;…

专业音视频领域中,Pro AV的崛起之路

编者按&#xff1a;在技术进步的加持下&#xff0c;AV行业发展得如何了&#xff1f;本文采访了两位深耕于广播电视行业的技术人&#xff0c;为我们介绍了专业音视频的进展&#xff1a;一位冉冉升起的新星&#xff1a;Pro AV以及FPGA在其中发挥的作用。 美国&#xff0c;拉斯维加…

RK3588 USB蓝牙调试

一.蓝牙基本概念 蓝牙技术是一种无线通信的方式&#xff0c;利用特定频率的波段&#xff08;2.4GHz-2.485GHz左右&#xff09;&#xff0c;进行电磁波传输。蓝牙传输原理是主从关系&#xff0c;一个主设备可以与7个蓝牙从设备配对。 二.蓝牙标准 蓝牙标准版本发展如下&#x…

自动化办公篇之python批量改名

#批量命名 import xlwings as xw app xw.App(visibleFalse,add_bookFalse) workbook app.books.open("测试表.xlsx") for sheet in workbook.sheets:sheet.namesheet.name.replace("彩印之","银河") workbook.save() app.quit()

C#中base关键字的使用

在C#编程语言中&#xff0c;base关键字扮演着重要的角色。它用于在派生类中调用基类的成员&#xff0c;并为开发人员提供了一种简单而有效的方式来实现继承以及重用代码。在本文中&#xff0c;我们将探讨base关键字的使用及其在C#中的代码实例。 首先&#xff0c;我们需要了解…