✅<1>主页::我的代码爱吃辣
📃<2>知识讲解:Linux——进程间通信——管道通信
☂️<3>开发环境:Centos7
💬<4>前言:进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。
目录
进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。
管道
System V IPC
POSIX IPC
头文件:#include
功能:创建一无名管道。
原型:int pipe(int fd[2]);
参数:fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端。
返回值:成功返回0,失败返回错误代码。
测试代码:
pipe.cc:
- #include
- #include
- #include
- #include
- int main(void)
- {
- int fds[2]; // f[0]管道读端,f[1]管道写端
- char buf[100];
- int len;
- // 创建管道
- if (pipe(fds) == -1)
- perror("make pipe"), exit(1);
-
- // read from stdin
- while (fgets(buf, 100, stdin))
- {
- len = strlen(buf);
- // 写入管道
- if (write(fds[1], buf, len) != len)
- {
- perror("write to pipe");
- break;
- }
-
- memset(buf, 0, sizeof(buf));
-
- // 从管道中读取
- if ((len = read(fds[0], buf, 100)) == -1)
- {
- perror("read from pipe");
- break;
- }
-
- // 写入显示器
- if (write(1, buf, len) != len)
- {
- perror("write to stdout");
- break;
- }
- }
- }
makefile:
- pipe:pipe.cc
- g++ -o $@ $^ -std=c++11
-
- .PHONY:clean
- clean:
- rm -rf pipe
测试结果:
我们知道fork之后,子进程会继承父进程的代码,数据会发生写时拷贝,那么父进程的文件描述符会不会被继承呢?会的。那么父进程创建的管道,其中的两个文件描述符一个指向管道的读端,一个指向管道的写端,也会被子进程继承。
管道的特点:
特殊场景:
测试代码:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- // 让不同的进程看到同一份资源!!!!
- // 任何一种任何一种进程间通信中,一定要 先 保证不同的进程之间看到同一份资源
- int pipefd[2] = {0}; // pipefd[0] 读端, pipe[1]写端
-
- // 1. 创建管道
- int n = pipe(pipefd);
- if (n < 0)
- {
- std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
- return 1;
- }
- std::cout << "pipefd[0]: " << pipefd[0] << std::endl; // 读端
- std::cout << "pipefd[1]: " << pipefd[1] << std::endl; // 写端
-
- // 2. 创建子进程
- pid_t id = fork();
- assert(id != -1);
-
- if (id == 0) // 子进程
- {
- // 3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
- close(pipefd[0]);
-
- // 4. 开始通信 -- 结合某种场景
- const std::string namestr = "hello,我是子进程";
- int cnt = 1;
- char buffer[1024];
- while (true)
- {
- snprintf(buffer, sizeof buffer, "%s, 计数器: %d, 我的PID: %d", namestr.c_str(), cnt++, getpid());
- write(pipefd[1], buffer, strlen(buffer));
- sleep(1);
- }
- // 退出时关闭打开的文件描述符
- close(pipefd[1]);
- exit(0);
- }
-
- // 父进程
- // 3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
- close(pipefd[1]);
-
- // 4. 开始通信 -- 结合某种场景
- char buffer[1024];
- int cnt = 0;
- while (true)
- {
- // sleep(10);
- // sleep(1);
- int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
- if (n > 0)
- {
- buffer[n] = '\0';
- std::cout << "我是父进程, child give me message: " << buffer << std::endl;
- }
- else if (n == 0)
- {
- std::cout << "我是父进程, 读到了文件结尾" << std::endl;
- break;
- }
- else
- {
- std::cout << "我是父进程, 读异常了" << std::endl;
- break;
- }
- sleep(1);
- if (cnt++ > 5)
- break;
- }
- // 父进程读端关闭,子进程会收到13号信号
- close(pipefd[0]);
-
- // 回收子进程的僵尸状态
- int status = 0;
- waitpid(id, &status, 0);
- std::cout << "子进程pid:" << id << ",收到的信号sig: " << (status & 0x7F) << std::endl;
-
- sleep(1);
-
- return 0;
- }
测试结果:
所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。
当没有数据可读时,read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
如果我们使用,父进程来控制写端,子进程进行读取,发送数据让子进程执行特定的任务,我们就可以实现对子进程的控制。
代码:
CtrlProc.cc:
- #include
- #include
- #include
- #include
- #include
- #include
- #include "Task.hpp"
-
- using namespace std;
- #define NUM_PROC 5
-
- struct child_pip
- {
- child_pip(int fd, pid_t pid)
- : _fd(fd), _pid(pid)
- {
- }
- ~child_pip()
- {
- }
-
- int _fd;
- pid_t _pid;
- };
-
- void WaitCommand()
- {
- Task task;
- int command;
- while (1)
- {
- size_t n = read(0, &command, sizeof(int));
- if (n == 4) // 读取成功
- {
- task.funcs[command]();
- }
- else if (n == 0) // 读取失败
- {
- break;
- }
- else
- {
- break;
- }
- }
- }
- void creatproc(vector
&child_pip_v) - {
- for (int i = 0; i < NUM_PROC; i++)
- {
- // 1.创建管道
- int pipfd[2];
- pipe(pipfd);
-
- // 2.创建子进程
- pid_t pid = fork();
- if (pid < 0)
- perror("fork");
-
- // 我们想让子进程从管道读,父进程向管道写
- if (pid == 0) // 子进程
- {
- // 3.关闭不必要的文件描述符
- close(pipfd[1]);
- // 3.1重定向,将来子进程指向0号文件描述符读取
- dup2(pipfd[0], 0);
-
- WaitCommand();
-
- exit(0);
- }
- // 父进程
- // 3.关闭不必要的文件描述符
- cout << "子进程pid:" << pid << endl;
- close(pipfd[0]);
-
- // 建立好子进程与管道的映射,父进程的写端口,和子进程pid
- child_pip_v.push_back(child_pip(pipfd[1], pid));
- }
- }
-
- void ctrlproc(vector
&child_pip_v) - {
- while (1)
- {
- int command = 0;
- cin >> command;
- if (command == -1)
- break;
- int i = rand() % NUM_PROC;
- write(child_pip_v[i]._fd, &command, sizeof(int));
- }
- }
-
- void waitproc(vector
&child_pip_v) - {
- int status = 0;
- for (int i = 0; i < child_pip_v.size(); i++)
- {
- close(child_pip_v[i]._fd);
- }
- // sleep(5);
- for (int i = 0; i < child_pip_v.size(); i++)
- {
- waitpid(child_pip_v[i]._pid, &status, 0);
- cout << "子进程:" << child_pip_v[i]._pid << "退出" << endl;
- }
- }
-
- int main()
- {
-
- vector
child_pip_v; - creatproc(child_pip_v);
-
- ctrlproc(child_pip_v);
-
- waitproc(child_pip_v);
-
- return 0;
- }
Task.cc:
- #include
- #include
- #include
-
- using namespace std;
- typedef void (*fun_t)();
-
- void beatxyy()
- {
- cout << "子进程:" << getpid() << ",执行数据库" << endl;
- }
-
- void beatxyf()
- {
- cout << "子进程:" << getpid() << ",写入日志" << endl;
- }
-
- void beatwy()
- {
- cout << "子进程:" << getpid() << ",读取网卡" << endl;
- }
-
- void beatwj()
- {
- cout << "子进程:" << getpid() << ",刷新缓冲区" << endl;
- }
-
- void beatxjy()
- {
- cout << "子进程:" << getpid() << ",数据比对" << endl;
- }
-
- struct Task
- {
- Task()
- {
- funcs.push_back(beatxyy);
- funcs.push_back(beatxyf);
- funcs.push_back(beatwy);
- funcs.push_back(beatwj);
- funcs.push_back(beatxjy);
- }
-
- vector<fun_t> funcs;
- };
makefile:
- CtrlProc:CtrlProc.cc
- g++ -o $@ $^ -std=c++11
-
- .PHONY:clean
- clean:
- rm -rf CtrlProc
测试结果:
注意:
为什么这里的waitproc我们要分开成两个循环,如果一个循环,文件描述符会无法关闭完,子进程也就无法退出。
我们关闭了第一个文件描述符,第一个管道由于继承问题,第一个管道还会有后面的子进程也会指向。最终导致我们只能有最后一个子进程先退出了,其他子进程进程陆续退出,此时进程等待也已经结束了,除了最后一个子进程。其他子进程的僵尸状态都没有被回收。