当前位置:   article > 正文

Linux进程间通信(管道)_linux 管道

linux 管道

目录

进程间通信的目的

进程间通信的必要性本质以及技术背景

管道 

匿名管道 

扩展 

命名管道


进程间通信的目的

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

进程间通信的必要性本质以及技术背景

进程间通信的必要性,单进程的,那么也就无法使用并发能力,更加无法实现多进程协同。传输数据,同步执行流,消息通知等,所以进程通信不是目的,而是手段。

进程间通信的技术背景

1.进程是具有独立性的。虚拟地址空间+页表保证进程运行的独立性(进程内核数据结构+进程的代码和数据)。

2.通信成本会比较高。

进程间通信的本质理解

1.进程间通信的前提,首先需要让不同的进程看到同一块"内存"(特定的结构组织的)

2.所以你所谓的进程看到同一块"内存",属于哪一个进程呢?不能隶属于任何一个进程,而应该更强调共享。 

管道 

管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
管道又分为两种,匿名管道和命名管道。

管道的原理:

这个就叫做管道,分别以读写的方式打开一个文件,fork()创建子进程,双方关闭自己不需要的文件描述符,管道就是文件,两个进程通过文件的方式进行通信。

匿名管道 

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码


其实从上面这张都就可以看出管道是怎们样的,接下来就用代码来实现一下。 

  1. #include <iostream>
  2. #include <string>
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <assert.h>
  6. #include <unistd.h>
  7. #include <sys/types.h>
  8. #include <sys/wait.h>
  9. using namespace std;
  10. // 为什么不定义全局buffer来进行通信呢?? 因为有写时拷贝的存在,无法更改通信!
  11. int main()
  12. {
  13. // 1. 创建管道
  14. int pipefd[2] = {0};
  15. int n = pipe(pipefd);
  16. assert(n == 0);
  17. (void)n;
  18. #ifdef DEBUG
  19. cout << "pipefd[0]: " << pipefd[0] << endl; // 3
  20. cout << "pipefd[1]: " << pipefd[1] << endl; // 4
  21. #endif
  22. // 2. 创建子进程
  23. pid_t id = fork();
  24. assert(id != -1);
  25. if (id == 0)
  26. {
  27. // 子进程 - 读
  28. // 3. 构建单向通信的信道,父进程写入,子进程读取
  29. // 3.1 关闭子进程不需要的fd
  30. close(pipefd[1]);
  31. char buffer[1024 * 8];
  32. while (true)
  33. {
  34. // sleep(20);
  35. // 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等
  36. // 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾!
  37. ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
  38. if (s > 0)
  39. {
  40. buffer[s] = '\0';
  41. cout << "child get a message[" << getpid() << "] Father# " << buffer << endl;
  42. }
  43. else if (s == 0)
  44. {
  45. cout << "writer quit(father), me quit!!!" << endl;
  46. break;
  47. }
  48. }
  49. exit(1);
  50. }
  51. // 父进程 - 写
  52. // 3. 构建单向通信的信道
  53. // 3.1 关闭父进程不需要的fd
  54. close(pipefd[0]);
  55. string message = "我是父进程,我正在给你发消息";
  56. char buffer[1024 * 8];
  57. int count = 0;
  58. char send_buffer[1024 * 8];
  59. while (true)
  60. {
  61. // 3.2 构建一个变化的字符串
  62. snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d",
  63. message.c_str(), getpid(), count++);
  64. // 3.3 写入
  65. write(pipefd[1], send_buffer, strlen(send_buffer));
  66. // 3.4 故意sleep
  67. sleep(1);
  68. cout << count << endl;
  69. if (count == 5)
  70. {
  71. cout << "writer quit(father)" << endl;
  72. break;
  73. }
  74. }
  75. close(pipefd[1]);
  76. pid_t ret = waitpid(id, nullptr, 0);
  77. cout << "id : " << id << " ret: " << ret << endl;
  78. assert(ret > 0);
  79. (void)ret;
  80. return 0;
  81. }

来讲一下这个代码的大致方向,第一部肯定是创建管道,然后就是创建子进程,这里我们是让父进程写子进程读,所以关闭父子进程不需要的fd,写入的一方,fd没有关闭,如果有数据,就读,没有数据就等,写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾,最后关闭父子进程fd,回收子进程。

其从上面的代码我们就可以总结一下管道的特点。总结管道的特点,理解以前的管道 | ,管道是一个文件–读取–具有访问控制,显示器也是一个文件,父子同时往显示器写入的时候,有没有说一个会等另一个的情况呢,缺乏访问控制。

1.管道是用来进行具有血缘关系的进程进性进程间通信-- 常用于父子通信 

2.管道具有通过让进程间协同,提供了访问控制

3.管道提供的是面向流式的通信服务--面向字节流--_协议 

4.管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的

5.管道是单向通信的,就是半双工通信的一种特殊情况 

6.写快,读慢,写满不能在写了

7.写慢,读快,管道没有数据的时候,读必须等待

8.写关,读0,标识读到了文件结尾

9.读关,写继续写,oS终止写进程

扩展 

分发多个任务的管理器

 代码:

  1. //.hpp
  2. //#pragma once
  3. #include <iostream>
  4. #include <string>
  5. #include <vector>
  6. #include <unordered_map>
  7. #include <unistd.h>
  8. #include <functional>
  9. typedef std::function<void()> func;
  10. std::vector<func> callbacks;
  11. std::unordered_map<int, std::string> desc;
  12. void readMySQL()
  13. {
  14. std::cout << "sub process[" << getpid() << " ] 执行访问数据库的任务\n" << std::endl;
  15. }
  16. void execuleUrl()
  17. {
  18. std::cout << "sub process[" << getpid() << " ] 执行url解析\n" << std::endl;
  19. }
  20. void cal()
  21. {
  22. std::cout << "sub process[" << getpid() << " ] 执行加密任务\n" << std::endl;
  23. }
  24. void save()
  25. {
  26. std::cout << "sub process[" << getpid() << " ] 执行数据持久化任务\n" << std::endl;
  27. }
  28. void load()
  29. {
  30. desc.insert({callbacks.size(), "readMySQL: 读取数据库"});
  31. callbacks.push_back(readMySQL);
  32. desc.insert({callbacks.size(), "execuleUrl: 进行url解析"});
  33. callbacks.push_back(execuleUrl);
  34. desc.insert({callbacks.size(), "cal: 进行加密计算"});
  35. callbacks.push_back(cal);
  36. desc.insert({callbacks.size(), "save: 进行数据的文件保存"});
  37. callbacks.push_back(save);
  38. }
  39. void showHandler()
  40. {
  41. for(const auto &iter : desc )
  42. {
  43. std::cout << iter.first << "\t" << iter.second << std::endl;
  44. }
  45. }
  46. int handlerSize()
  47. {
  48. return callbacks.size();
  49. }
  50. //.cc
  51. #include <iostream>
  52. #include <vector>
  53. #include <cstdlib>
  54. #include <ctime>
  55. #include <cassert>
  56. #include <unistd.h>
  57. #include <sys/wait.h>
  58. #include <sys/types.h>
  59. #include "Task.hpp"
  60. #define PROCESS_NUM 5
  61. using namespace std;
  62. int waitCommand(int waitFd, bool &quit) // 如果对方不发,我们就阻塞
  63. {
  64. uint32_t command = 0;
  65. ssize_t s = read(waitFd, &command, sizeof(command));
  66. if (s == 0)
  67. {
  68. quit = true;
  69. return -1;
  70. }
  71. assert(s == sizeof(uint32_t));
  72. return command;
  73. }
  74. void sendAndWakeup(pid_t who, int fd, uint32_t command)
  75. {
  76. write(fd, &command, sizeof(command));
  77. cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
  78. }
  79. int main()
  80. {
  81. // 代码中关于fd的处理,有一个小问题,不影响我们使用,但是你能找到吗??
  82. load();
  83. // pid: pipefd
  84. vector<pair<pid_t, int>> slots;
  85. // 先创建多个进程
  86. for (int i = 0; i < PROCESS_NUM; i++)
  87. {
  88. int pipefd[2] = {0};
  89. int n = pipe(pipefd);
  90. assert(n == 0);
  91. (void)n;
  92. pid_t id = fork();
  93. assert(id != -1);
  94. if (id == 0)
  95. {
  96. close(pipefd[1]);
  97. while (true)
  98. {
  99. // pipefd[0]
  100. // 等命令
  101. bool quit = false;
  102. int command = waitCommand(pipefd[0], quit); // 如果对方不发,我们就阻塞
  103. if (quit)
  104. break;
  105. // 执行对应的命令
  106. if (command >= 0 && command < handlerSize())
  107. {
  108. callbacks[command]();
  109. }
  110. else
  111. {
  112. cout << "非法command: " << command << endl;
  113. }
  114. }
  115. exit(0);
  116. }
  117. // father,进行写入,关闭读端
  118. close(pipefd[0]); // pipefd[1]
  119. slots.push_back(pair<pid_t, int>(id, pipefd[1]));
  120. }
  121. // 父进程派发任务
  122. srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 让数据源更随机
  123. while (true)
  124. {
  125. // 选择一个任务, 如果任务是从网络里面来的?
  126. int command = rand() % handlerSize();
  127. // 选择一个进程 ,采用随机数的方式,选择进程来完成任务,随机数方式的负载均衡
  128. int choice = rand() % slots.size();
  129. // 把任务给指定的进程
  130. sendAndWakeup(slots[choice].first, slots[choice].second, command);
  131. sleep(1);
  132. // int select;
  133. // int command;
  134. // cout << "############################################" << endl;
  135. // cout << "# 1. show funcitons 2.send command #" << endl;
  136. // cout << "############################################" << endl;
  137. // cout << "Please Select> ";
  138. // cin >> select;
  139. // if (select == 1)
  140. // showHandler();
  141. // else if (select == 2)
  142. // {
  143. // cout << "Enter Your Command> ";
  144. // // 选择任务
  145. // cin >> command;
  146. // // 选择进程
  147. // int choice = rand() % slots.size();
  148. // // 把任务给指定的进程
  149. // sendAndWakeup(slots[choice].first, slots[choice].second, command);
  150. // }
  151. // else
  152. // {
  153. // }
  154. }
  155. // 关闭fd, 所有的子进程都会退出
  156. for (const auto &slot : slots)
  157. {
  158. close(slot.second);
  159. }
  160. // 回收所有的子进程信息
  161. for (const auto &slot : slots)
  162. {
  163. waitpid(slot.first, nullptr, 0);
  164. }
  165. }

命名管道

 命名管道可以从命令行上创建,命令行方法是使用下面这个命令 mkfifo filename                         删除管道文件:unlink filename
命名管道也可以从程序里创建,相关函数是:

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

接下来同样用一段代码来演示一下:

  1. //Makefile
  2. .PHONY:all
  3. all:client mutiServer
  4. client:client.cxx
  5. g++ -o $@ $^ -std=c++11
  6. mutiServer:server.cxx
  7. g++ -o $@ $^ -std=c++11
  8. .PHONY:clean
  9. clean:
  10. rm -f client mutiServer
  11. //client.cxx
  12. #include "comm.hpp"
  13. int main()
  14. {
  15. // 1. 获取管道文件
  16. int fd = open(ipcPath.c_str(), O_WRONLY);
  17. if(fd < 0)
  18. {
  19. perror("open");
  20. exit(1);
  21. }
  22. // 2. ipc过程
  23. string buffer;
  24. while(true)
  25. {
  26. cout << "Please Enter Message Line :> ";
  27. std::getline(std::cin, buffer);
  28. write(fd, buffer.c_str(), buffer.size());
  29. }
  30. // 3. 关闭
  31. close(fd);
  32. return 0;
  33. }
  34. //log.hpp
  35. #ifndef _LOG_H_
  36. #define _LOG_H_
  37. #include <iostream>
  38. #include <ctime>
  39. #define Debug 0
  40. #define Notice 1
  41. #define Warning 2
  42. #define Error 3
  43. const std::string msg[] = {
  44. "Debug",
  45. "Notice",
  46. "Warning",
  47. "Error"
  48. };
  49. std::ostream &Log(std::string message, int level)
  50. {
  51. std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
  52. return std::cout;
  53. }
  54. #endif
  55. //comm.hpp
  56. #ifndef _COMM_H_
  57. #define _COMM_H_
  58. #include <iostream>
  59. #include <string>
  60. #include <cstdio>
  61. #include <cstring>
  62. #include <unistd.h>
  63. #include <sys/types.h>
  64. #include <sys/stat.h>
  65. #include <fcntl.h>
  66. #include "Log.hpp"
  67. using namespace std;
  68. #define MODE 0666
  69. #define SIZE 128
  70. string ipcPath = "./fifo.ipc";
  71. #endif
  72. //server.cxx
  73. #include "comm.hpp"
  74. #include <sys/wait.h>
  75. static void getMessage(int fd)
  76. {
  77. char buffer[SIZE];
  78. while (true)
  79. {
  80. memset(buffer, '\0', sizeof(buffer));
  81. ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
  82. if (s > 0)
  83. {
  84. cout <<"[" << getpid() << "] "<< "client say> " << buffer << endl;
  85. }
  86. else if (s == 0)
  87. {
  88. // end of file
  89. cerr <<"[" << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl;
  90. break;
  91. }
  92. else
  93. {
  94. // read error
  95. perror("read");
  96. break;
  97. }
  98. }
  99. }
  100. int main()
  101. {
  102. // 1. 创建管道文件
  103. if (mkfifo(ipcPath.c_str(), MODE) < 0)
  104. {
  105. perror("mkfifo");
  106. exit(1);
  107. }
  108. Log("创建管道文件成功", Debug) << " step 1" << endl;
  109. // 2. 正常的文件操作
  110. int fd = open(ipcPath.c_str(), O_RDONLY);
  111. if (fd < 0)
  112. {
  113. perror("open");
  114. exit(2);
  115. }
  116. Log("打开管道文件成功", Debug) << " step 2" << endl;
  117. int nums = 3;
  118. for (int i = 0; i < nums; i++)
  119. {
  120. pid_t id = fork();
  121. if (id == 0)
  122. {
  123. // 3. 编写正常的通信代码了
  124. getMessage(fd);
  125. exit(1);
  126. }
  127. }
  128. for(int i = 0; i < nums; i++)
  129. {
  130. waitpid(-1, nullptr, 0);
  131. }
  132. // 4. 关闭文件
  133. close(fd);
  134. Log("关闭管道文件成功", Debug) << " step 3" << endl;
  135. unlink(ipcPath.c_str()); // 通信完毕,就删除文件
  136. Log("删除管道文件成功", Debug) << " step 4" << endl;
  137. return 0;
  138. }

上面的代码其实就是实现server进程和client进程间的通信,其实从这些代码也可以看出来,匿名管道适用于父子进程间的通信,命名管道用于两个毫不相关的进程间通信。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/212045
推荐阅读
相关标签
  

闽ICP备14008679号