1. vscode软件下载和使用
1.1 下载
1.1.1 解决下载慢问题
链接:https://blog.csdn.net/wang13679201813/article/details/125367532
1.1.2 推荐下载链接
1.2 vscode是什么
vscode(visual studio code)是一个编辑器,不是编译器,这里我们使用vscode+centos:也就是windows+linux开发,使用C++语言,这里用vscode取代Linux的vim。
1.3 Windows本地vscode使用
刚下载的桌面就是这样的:
打开目录后按照桌面对应路径找到刚刚创建的文件,打开即可。
通过上面这个框就可以新建文件和新建目录等操作。这里不支持编译和运行。
1.4 远程连接linux
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmDdkau8-1681550357620)(https://jinyinhan.oss-cn-beijing.aliyuncs.com/QQ截图20230405132005.png)]
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EYMrX7wf-1681550357621)(https://jinyinhan.oss-cn-beijing.aliyuncs.com/QQ截图20230405132821.png)]
重启后就可以看到对应的SSH下就有对应的主机。后续就是连接新的Window,然后选择Linux,输入密码就OK了。连接成功后,Linux主机名会显示一个绿色的勾勾。后续操作按照对应的来就OK了,不演示了。
远程运行直接使用快捷方式:ctrl+s,或者点击最上面框架中的Terminal终端,new一个新的终端即可。
1.5 推荐插件
- C/C++
- C/C++ Extension Pack
- C/C++ Themes
- Chinese
- vscode-icons
- file-size
- GBK to UTF8 for vscode
- GDB Dubug(不建议使用vscode搭建的远程调试),这里最好使用Linux的gdb调试
2. 进程间通信目的
- 数据传输(一个进程需要把自己的数据传输给另外一个进程)
- 资源共享(多个进程之间共享同样的资源)
- 通知事件(一个进程需要向另外一个或多个进程发送消息,通知它们发生某种事件)
- 进程控制(有些进程需要完全控制另外一个进程的执行)
3. 为什么需要通信
进程具有独立性,需要让独立进程通信就需要成本。不能让一个进程直接访问另外一个进程的"资源",不能违背进程具有独立性,所以让两个程序通信前提条件是:先让两个进程看到同一份“资源”,不直接访问,"资源"就由操作系统来提供!
4. 匿名管道
管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
[jyh@VM-12-12-centos study17]$ who //查看当前正在登陆的用户 jyh pts/0 2023-04-05 16:30 (171.113.60.75) [jyh@VM-12-12-centos study17]$ who | wc -l //wc是用来统计个数 1 [jyh@VM-12-12-centos study17]$
上述who是一个命令,本质它是一个进程,一个bash的子进程,wc同样是一个bash的子进程,通过管道的方式把who进程的数据也让wc进程看到。
如何理解呢?首先Linux下一切皆文件,管道也是文件,who进程一般都是从标准输出中获取的信息,wc进程一般都是从标准输入中获取信息的,将who进程的标准输出重定向到管道这个文件中,wc进程则将标准输入重定向到管道文件中,所以这样就可以实现两个进程看到同一份“资源”。
4.1 原理
创建子进程,只会复制进程相关的数据结构对象,并不会拷贝父进程的文件对象。所以上述两个进程中文件描述符表中每个指针的指向的都是同一个文件对象。这让就可以解析一个现象:fork之后,父子进程会向同一个显示器打印数据的原因。这里OS提供的内存文件就是管道文件,这样就可以达成共享同一"资源"。(这里管道文件不是放在磁盘中的,是一个内存文件,它只是支持单向通信)。另外管道文件是确定数据流向的,关闭不需要的fd,也就是当who进程进行write的时候,wc进程进行read的时候,此时who进程就会关闭对应的read对应的描述符,wc进程就会关闭对应的write对应的描述符,就可以解析清楚上一个管道的案例的现象了。(这里的这个管道也就是匿名管道,不知道它对应的路径,也不知道对应的文件名等等。
4.2 代码案例
选项 | pipe()系统调用 |
声明 | int pipe(int pipefd[2]); |
头文件 | #include <unistd.h> |
作用 | 创建管道 |
详细描述 | Pipe()创建一个管道,这是一个单向数据通道,可用于进程间通信。数组pipefd用于返回2引用管道两端的文件描述符。Pipefd[0]表示管道的读端Pipefd[1]表示写端管道。写入管道写端的数据被内核缓冲,直到从管道的读端读取。 |
返回值 | 如果成功,则返回0。如果出现错误,则返回-1,并适当地设置errno。 |
#include <iostream> #include <unistd.h> #include <cerrno> #include <cstring> #include <cassert> #include <cstdlib> #include <string> #include <stdio.h> #include <sys/types.h> using namespace std; int main() { //1. 创建管道 int pipefd[2] = {0}; int fd_result = pipe(pipefd); if(fd_result == -1) { cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl; } cout << "pipefd[0]:" << pipefd[0] << endl; //fd:3 -> 读端 cout << "pipefd[1]:" << pipefd[1] << endl; //fd:4 -> 写端 //2. 创建子进程 pid_t id = fork(); assert(id != -1); if(id == 0) //child(写入) { close(pipefd[0]); //关闭读端 //4. 开始通信 const string name_str = "hello, i am child"; int cnt = 1; char buffer_child[1024]; while(true) { snprintf(buffer_child, sizeof(buffer_child), "name:%s, cnt:%d, my_pid:%d", name_str.c_str(), cnt++, getpid()); write(pipefd[1], buffer_child, strlen(buffer_child)); sleep(1); } exit(0); } //parent(读取): //3. 关闭不需要的fd(父进程读取,子进程写入) close(pipefd[1]); //关闭写端 char buffer_parent[1024]; while(true) { int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1); if(n == -1) { cout << "call read() is failed" << "errno=" << errno << ":" << strerror(errno) << endl; } buffer_parent[n] = '\0'; cout << "i am parent, child give me message:" << buffer_parent << endl; } return 0; }
五个特点
管道是单向通信
管道本质是文件,文件描述符声明周期是随进程的,管道的声明周期是随进程的
不仅父子进程可以通信,爷孙进程也可以通信,兄弟进程也可以通信,所以管道通信通常用来具有"血缘关系"的进程进行进程间通信。(pipe()打开管道并不清楚管道名字 --> 匿名管道)
写入的次数和读取的次数不是严格匹配的,可能一次写的东西分很多次读,可能多次写的东西一次读取,读写没有强相关
如果一个进程read端读取完管道所有数据,对方如果不发,那么只能等待;如果write端写满管道了,不能够写了;管道具有一定的协同能力,让读端和写端能够按照顺序通信(自带同步机制)
两个场景
- 关闭写端,读端没有关闭
#include <iostream> #include <unistd.h> #include <cerrno> #include <cstring> #include <cassert> #include <cstdlib> #include <string> #include <stdio.h> #include <sys/types.h> using namespace std; int main() { //1. 创建管道 int pipefd[2] = {0}; int fd_result = pipe(pipefd); if(fd_result == -1) { cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl; } cout << "pipefd[0]:" << pipefd[0] << endl; //fd:3 -> 读端 cout << "pipefd[1]:" << pipefd[1] << endl; //fd:4 -> 写端 //2. 创建子进程 pid_t id = fork(); assert(id != -1); if(id == 0) //child(写入) { close(pipefd[0]); //关闭读端 //4. 开始通信 int cnt = 0; while(true) { char x = 'A'; write(pipefd[1], &x, 1); cout << "cnt:" << cnt << endl; sleep(3); break; } close(pipefd[1]); exit(0); } //parent(读取): //3. 关闭不需要的fd(父进程读取,子进程写入) close(pipefd[1]); //关闭写端 char buffer_parent[1024]; while(true) { int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1); if(n == -1) { cout << "read is anomaly" << endl; } if(n == 0){ cout << "read end of file" << endl; break; } buffer_parent[n] = '\0'; cout << "i am parent, child give me message:" << buffer_parent << endl; } close(pipefd[0]); return 0; } //输出结果: //pipefd[0]:3 //pipefd[1]:4 //i am parent, child give me message:A //cnt:0 //read end of file
所以,关闭写端,读端没有关闭,读端再去读取数据,read就会返回0,那么就读到了文件结尾。
- 关闭读端,写端没有关闭
#include <iostream> #include <unistd.h> #include <cerrno> #include <cstring> #include <cassert> #include <cstdlib> #include <string> #include <stdio.h> #include <sys/types.h> using namespace std; int main() { //1. 创建管道 int pipefd[2] = {0}; int fd_result = pipe(pipefd); if(fd_result == -1) { cout << "call pipe() is failed" << "errno=" << errno << ":" << strerror(errno) << endl; } cout << "pipefd[0]:" << pipefd[0] << endl; //fd:3 -> 读端 cout << "pipefd[1]:" << pipefd[1] << endl; //fd:4 -> 写端 //2. 创建子进程 pid_t id = fork(); assert(id != -1); if(id == 0) //child(写入) { close(pipefd[0]); //关闭读端 //4. 开始通信 int cnt = 0; while(true) { char x = 'A'; write(pipefd[1], &x, 1); cout << "cnt:" << cnt << endl; sleep(1); } close(pipefd[1]); exit(0); } //parent(读取): //3. 关闭不需要的fd(父进程读取,子进程写入) close(pipefd[1]); //关闭写端 char buffer_parent[1024]; while(true) { //sleep(10); int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1); if(n == -1) { cout << "read is anomaly" << endl; break; } if(n == 0){ cout << "read end of file" << endl; break; } buffer_parent[n] = '\0'; cout << "i am parent, child give me message:" << buffer_parent << endl; sleep(1); break; } close(pipefd[0]); return 0; } //输出结果: //pipefd[0]:3 //pipefd[1]:4 //i am parent, child give me message:A //cnt:0
关闭读端,写端没有关闭,这样就没有意义,操作系统不会维护不会浪费资源,OS会通过信号直接kill掉一直在写入的进程。
4.3 玩一玩(进程池)
4.3.1 模型
4.3.2 代码
- processPool.hpp
#include <iostream> #include <assert.h> #include <unistd.h> #include <vector> #include <string> #include <sys/types.h> #include <sys/wait.h> class manage_child; class Task; #define CHILD_PROCESS_NUM 3 void create_frame(std::vector<manage_child>& manage); void control_child_process(const std::vector<manage_child>& manage); void recycle_process(const std::vector<manage_child>& manage); class manage_child { public: manage_child(int fd, int id) :_write_fd(fd) ,_child_id(id) {} ~manage_child(){} public: int _write_fd; pid_t _child_id; }; typedef void(*fun_p)(); #define GET_NET 1 #define POLL_SERVER 2 #define PUSH_SOURCE 3 void get_net(); void poll_server(); void push_source(); class Task { public: Task(){ funcs.push_back(get_net); funcs.push_back(poll_server); funcs.push_back(push_source); } void execute_task(int option){ assert(option >= 1 && option <= 3); funcs[option - 1](); } ~Task(){} private: std::vector<fun_p> funcs; };
- main.cc
#include "processPool.hpp" int main() { //创建框架:一个父进程控制多个子进程(父进程写,子进程读) std::vector<manage_child> manage; create_frame(manage); //父进程控制任意子进程执行任务 control_child_process(manage); //回收子进程并关闭管道 recycle_process(manage); return 0; }
- processPool.cc
#include "processPool.hpp" void get_net() { std::cout << "子进程:PID:" << getpid() << "获取网络资源中........." << std::endl; } void poll_server() { std::cout << "子进程:PID:" << getpid() << "下载服务资源中........" << std::endl; } void push_source() { std::cout << "子进程:PID:" << getpid() << "发送服务资源中........" << std::endl; } void execute_command(int read_fd) { Task task; while(true){ int command = 0; int read_return = read(read_fd, &command, sizeof(int)); if(read_return == sizeof(int)){ //执行任务 task.execute_task(command); }else{ break; } } } void create_frame(std::vector<manage_child>& manage) { std::vector<int> fds; for(int i = 0; i < CHILD_PROCESS_NUM; ++i){ //创建管道 int pipefd[2]; int pipe_return = pipe(pipefd); assert(pipe_return == 0); (void)pipe_return; //创建子进程 pid_t id = fork(); assert(id != -1); if(id == 0){ //子进程 - 读 for(auto& fd : fds) close(fd); //关闭子进程拷贝父进程的写端 close(pipefd[1]); //dup2(pipefd[0], 0); execute_command(pipefd[0]); close(pipefd[0]); exit(0); } //父进程 close(pipefd[0]); //父进程对子进程和写端组织管理 manage.push_back(manage_child(pipefd[1], id)); fds.push_back(pipefd[1]); } } void show_option(){ std::cout << "-----------------------------------------" << std::endl; std::cout << "--- 1. 获取网络资源 2. 下载服务资源 ---" << std::endl; std::cout << "--- 3. 发送服务资源 4. 退出服务系统 ---" << std::endl; std::cout << "-----------------------------------------" << std::endl; } void control_child_process(const std::vector<manage_child>& manage) { while(true){ //选择任务 show_option(); std::cout << "请选择->"; int command = 0; std::cin >> command; if(command == 4) break; if(command < 1 || command > 3) continue; //选择进程 int rand_index = rand() % manage.size(); std::cout << "被选中的子进程:" << manage[rand_index]._child_id << std::endl; //分发任务 write(manage[rand_index]._write_fd, &command, sizeof(command)); sleep(1); } } void recycle_process(const std::vector<manage_child>& manage) { for(int i = 0; i < manage.size(); ++i){ std::cout << "父进程让子进程退出" << manage[i]._child_id << std::endl; close(manage[i]._write_fd); waitpid(manage[i]._child_id, nullptr, 0); //阻塞式等待 std::cout << "父进程回收子进程:" << manage[i]._child_id << std::endl; } sleep(5); //父进程退出 }