【并发编程三】C++实现通信——管道(pipe)

  • 一、管道(pipe)
  • 二、匿名管道
    • 1、简介
    • 2、父子进程:匿名管道的通信过程?
    • 3、相关函数
      • 3.1、创建管道CreatePipe
      • 3.2、写入管道WriteFile
      • 3.3、读取管道ReadFile
      • 3.4、获取句柄GetStdHandle
    • 4、demo
    • 5、结果
  • 三、有名管道
    • 1、简介
    • 2、两个进程间:命名管道的工作过程
    • 3、相关函数
      • 3.1、创建命名管道CreateNamedPipe
      • 3.2、监听请求ConnectNamedPipe
      • 3.3、等候一个命名管道实例WaitNamedPipe
    • 4、demo
    • 5、输出

一、管道(pipe)

所谓的管道,就是内核⾥⾯的⼀串缓存(Pipe)。一个进程从管道的⼀端写⼊的数据,实际上是缓存在内核中的,另⼀端读取,也就是从内核中读取这段数据。

  • 有两种类型的管道:匿名管道,有名管道(也叫命名管道)

二、匿名管道

1、简介

匿名管道只能用于父子进程间通信 ,不能跨网络通信,并且通信是单向。另外,管道传输的数据是⽆格式的流且⼤⼩受限。

正常情况下,控制台进程的输输入出是在控制台窗口的,但是如果我们在创建子进程的时候指定了其输入输出,那么子进程就会从我们的管道读数据,把输出数据写到我们指定的管道。
这就是我们代码中重定向标准输入的原因,否则system(“pause”)会无效。

2、父子进程:匿名管道的通信过程?

父进程写管道、子进程读管道,过程如下。

  • 1、父进程,CreatePipe()创建管道会得到两个句柄,即,管道的读句柄和管道的写句柄。
  • 2、父进程执行CreateProcess()启动子进程时,可以把句柄传(比如把读句柄传给子进程)递给子进程,写句柄父进程自己保存。
  • 3、父进程调用调用WriteFile()将数据写入到管道。
  • 4、子进程调用GetStdHandle()取得管道的读句柄,
  • 5、然后,子进程ReadFile()从管道读取出数据。

这样就做到了两个进程各有一个句柄(父进程写句柄、子进程读句柄),两个进程就可以通过各⾃的句柄 写⼊和读取同⼀个管道⽂件实现跨进程通信。

  • 以上流程说的是父进程写管道,子进程读管道。当然,也可以反过来,子进程写、父进程读,那就是父进程把写句柄给子进程,父进程保存读句柄)

3、相关函数

3.1、创建管道CreatePipe

BOOL CreatePipe(
PHANDLE hReadPipe; //指向管道读句柄
PHANDLE hWritePipe; //指向管道写句柄
LPSECURITY_ATTRIBUTES lpPipeAttributes;  //指向管道安全属性
DWORD nSize; //管道大小
)
  • linux系统下对应的创建匿名管道的函数是

3.2、写入管道WriteFile

BOOL WriteFile(HANDLE  hFile,//文件句柄LPCVOID lpBuffer,//数据缓存区指针DWORD   nNumberOfBytesToWrite,//要写的字节数LPDWORD lpNumberOfBytesWritten,//用于保存实际写入字节数的存储区域的指针LPOVERLAPPED lpOverlapped//OVERLAPPED结构体指针
);

3.3、读取管道ReadFile

BOOL ReadFile(HANDLE hFile, //文件的句柄LPVOID lpBuffer, //接收数据的缓冲区DWORD nNumberOfBytesToRead,    //读取的字节数LPDWORD lpNumberOfBytesRead,    //指向实际读取字节数的指针LPOVERLAPPED lpOverlapped//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);

3.4、获取句柄GetStdHandle

该函数用于取得指定的标准设备的句柄(标准输入,标准输出或标准错误),本文demo用于:子进程获取父进程传递的读句柄。

HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle);

GetStdHandle函数的参数可以是下列之一

参数 含义
STD_INPUT_HANDLE 标准输入句柄
STD_OUTPUT_HANDLE 标准输出句柄
STD_ERROR_HANDLE 标准错误句柄

4、demo

  • 父进程
//main.cpp
#include <windows.h>
#include "iostream"
#define BUFSIZE 4096
using namespace std;int main(int argc, char* argv[])
{cout << "\n ** This is a message from the father process. ** \n";cout << "第一步:创建管道" << endl;// Set the bInheritHandle flag so pipe handles are inherited. SECURITY_ATTRIBUTES saAttr;saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);saAttr.bInheritHandle = TRUE;saAttr.lpSecurityDescriptor = NULL;// Create a pipe for the child process's STDIN. HANDLE handle_read;HANDLE handle_write;bool ret = CreatePipe(&handle_read, &handle_write, &saAttr, 0);if (!ret){cout << "创建进程失败:create pipe fail" << endl;}//设置写句柄不可以被子进程继承,不设置也不影响。 Ensure the write handle to the pipe for STDIN is not inherited. if (!SetHandleInformation(handle_write, HANDLE_FLAG_INHERIT, 0)){cout << "设置句柄失败:set handle fail!" << endl;}cout << "第一步:子进程、设置管道句柄的继承" << endl; {// Create the child process. char cmdline[] = "childprocess.exe";PROCESS_INFORMATION piProcInfo;// Set up members of the PROCESS_INFORMATION structure. ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));// Set up members of the STARTUPINFO structure. // This structure specifies the STDIN handles for redirection.STARTUPINFO si;ZeroMemory(&si, sizeof(STARTUPINFO));si.cb = sizeof(STARTUPINFO);si.hStdInput = handle_read; //把管道的读句柄传给子进程si.dwFlags |= STARTF_USESTDHANDLES;// Create the child process.ret = CreateProcess(NULL, cmdline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &piProcInfo);if (!ret)cout << "创建子进程失败:create child process faile!";else{cout << "创建子进程成功:create child process sucess!";// Close handles to the child process and its primary thread.CloseHandle(piProcInfo.hProcess);CloseHandle(piProcInfo.hThread);CloseHandle(handle_read);}}cout << "第三步:向管道中写入数据:write to pipe." << endl;{// write contents to the pipe for the child's STDIN.DWORD len;char chBuf[BUFSIZE] = " hello pipe";for (int i = 0; i < 10; i++){bool ret = WriteFile(handle_write, chBuf, sizeof(chBuf), &len, NULL);//子进程读了后,父进程才可以继续写入管道if (!ret){cout << "写入管道数据失败。i=" << i << endl;break;}else {cout << "send data " << i << " is:" << chBuf << endl;}}cout << "写入管道数据结束。" << endl;// Close the pipe handle so the child process stops reading. if (!CloseHandle(handle_write))cout << "colse handle fail" << endl;}return 0;
}
#CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)PROJECT(process)ADD_EXECUTABLE(process main.cpp)ADD_SUBDIRECTORY(childprocess)SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
  • 子进程
// main.cpp
#include <windows.h>
#include"iostream"
#define BUFSIZE 4096
using namespace std;int main(int argc, char* argv[])
{ cout<<"\n ** This is a message from the child process. ** \n";CHAR chBuf[BUFSIZE]; DWORD len; HANDLE handle_read; handle_read = GetStdHandle(STD_INPUT_HANDLE);if (handle_read == INVALID_HANDLE_VALUE)ExitProcess(1); for (int i = 0;i<5;i++){ // Read from standard input and stop on error or no data.bool ret = ReadFile(handle_read, chBuf, BUFSIZE, &len, NULL);if (!ret || len == 0){cout << "读取数据失败" << endl;break;}cout << "receive data "<<i<<" is:" << chBuf << endl;} cout << "读取数据结束" << endl;Sleep(5000);freopen("CON", "r", stdin);    // 重定向输入,否则system("pause")会无效//CloseHandle(handle_read);system("pause");return 0;
}
#CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)SET(TARGET "childprocess")ADD_EXECUTABLE(childprocess main.cpp)SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")

5、结果

  • 父进程
  • 子进程
  • 后记

1.子进程取出管道中的数据后,父进程才可以继续往管道中写入数据
2.父进程在输入第5个send data后,子进程并没有读取。但是当子的sleep结束后,不知什么原因,不确定第5次的输入是被谁读走的。
3.子进程的sleep函数之前的输出如下

三、有名管道

1、简介

命名管道,顾名思义,这个管道肯定是有名字的。通过管道的名字来确保多个进程访问同一个管道。事实上,命名管道不仅可在同一台计算机的不同进程之间传输数据,甚至能在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。

命名管道的服务器和客户机的区别在于:
服务器是唯一一个有权创建命名管道的进程,也只有他才能接受管道客户机的连接请求。
而客户机只能同一个线程的命名管道服务器建立连接。

  • 命名管道具有很好的使用灵活性,表现在:
      1) 既可用于本地,又可用于网络。
      2) 可以通过它的名称而被引用。
      3) 支持多客户机连接。
      4) 支持双向通信。
      5)支持异步重叠I/O操作

2、两个进程间:命名管道的工作过程

  • 3.2 服务器
    命名管道服务器,步骤如下:
    1)使用API函数CreateNamedPipe,创建一个命名管道实例句柄。
    2)使用API函数ConnectNamedPipe,在命名管道实例上监听客户机连接请求。
    3)分别使用ReadFile和WriteFile这两个API函数,从客户机接收数据,或将数据发给客户机。
    4)使用API函数DisconnectNamedPipe,关闭命名管道连接。
    5)使用API函数CloseHandle,关闭命名管道实例句柄。
  • 3.2 客户端
    命名管道客户机,步骤如下:
    1)调用API函数WaitNamedPipe,等候一个命名管道实例可供使用。
    2)调用API函数CreateFile,打开命名管道实例并建立连接。
    3)调用API函数WriteFile和ReadFile,分别向服务器发送数据和从中接收数据。
    4)调用API函数CloseHandle,关闭打开的命名管道会话。

3、相关函数

3.1、创建命名管道CreateNamedPipe

创建命名管道的实例,并返回后续管道操作的句柄。

HANDLE CreateNamedPipeA([in]           LPCSTR                lpName,[in]           DWORD                 dwOpenMode,[in]           DWORD                 dwPipeMode,[in]           DWORD                 nMaxInstances,[in]           DWORD                 nOutBufferSize,[in]           DWORD                 nInBufferSize,[in]           DWORD                 nDefaultTimeOut,[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

CreateNamedPipe函数接口中的第一个参数lpName: .\pipe\pipename, 必须为这种格式。中间的“.”表示本地机器,如果要跟远程机器建立连接,则需要设定远程服务器的名字。

详细解释请参考:CreateNamedPipeA 函数 (winbase.h)

3.2、监听请求ConnectNamedPipe

在命名管道实例上监听客户机连接请求。

BOOL ConnectNamedPipe([in]                HANDLE       hNamedPipe,[in, out, optional] LPOVERLAPPED lpOverlapped
);

详细解释请参考:ConnectNamedPipe 函数 (namedpipeapi.h)

3.3、等候一个命名管道实例WaitNamedPipe

等到超时间隔过或指定命名管道的实例可用于连接

BOOL ConnectNamedPipe([in]                HANDLE       hNamedPipe,[in, out, optional] LPOVERLAPPED lpOverlapped
);

详细解释请参考:WaitNamedPipeA 函数 (winbase.h)

4、demo

  • 服务端
#include <iostream>
#include <windows.h>
using namespace std;int main()
{printf("创建命名管道并等待连接\n");char pipeName[] = "\\\\.\\Pipe\\mypipe";HANDLE hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, 0);//waiting to be connectedif (ConnectNamedPipe(hPipe, NULL) != NULL){printf("连接成功,开始发送数据\n");DWORD    dwWrite;const char* pStr = "data from server";if (!WriteFile(hPipe, pStr, strlen(pStr), &dwWrite, NULL)){cout << "write failed..." << endl << endl;return 0;}cout << "sent data: " << endl << pStr << endl << endl;}DisconnectNamedPipe(hPipe);CloseHandle(hPipe);//关闭管道printf("关闭管道\n");system("pause");
}
  • 客户端
// ClientPip.cpp #include <iostream>
#include <windows.h>
using namespace std;
#define BUFSIZE 5int main()
{printf("命名管道:客户端上线\n");printf("按任意键以开始连接命名管道\n");getchar();printf("开始等待命名管道\n");char pipeName[] = "\\\\.\\Pipe\\mypipe";if (WaitNamedPipe(pipeName, NMPWAIT_WAIT_FOREVER) == FALSE)return 0;printf("打开命名管道\n");HANDLE hPipe = CreateFile(pipeName, GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if ((long)hPipe == -1)return 0;//接收服务端发回的数据BOOL fSuccess = false;DWORD len = 0;char buffer[BUFSIZE];string recvData = "";do{fSuccess = ReadFile(hPipe, buffer, BUFSIZE * sizeof(char), &len, NULL);char buffer2[BUFSIZE + 1] = { 0 };memcpy(buffer2, buffer, len);recvData.append(buffer2);if (!fSuccess || len < BUFSIZE)break;} while (true);cout << "recv data:" << endl << recvData.c_str() << endl << endl;FlushFileBuffers(hPipe);DisconnectNamedPipe(hPipe);CloseHandle(hPipe);system("pause");return 0;
}

5、输出

参考:
1、C++的创建命名管道(CreateNamedPipe)
2、C++实现进程通信(管道pipe)

【并发编程三】C++进程通信——管道(pipe)相关推荐

  1. python并发编程之semaphore(信号量)_python 之 并发编程(守护进程、互斥锁、IPC通信机制)...

    9.5 守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就立即终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic process ...

  2. Python3 与 C# 并发编程之~进程先导篇

    在线预览:http://github.lesschina.com/python/base/concurrency/1.并发编程-进程先导篇.html Python3 与 C# 并发编程之- 进程篇:h ...

  3. 操作系统实验三、进程通信

    文章目录 操作系统实验三.进程通信 一.实验目的 二.实验内容 三.设计原理(或方案)及相关算法 四.结果分析 五.源代码 操作系统实验三.进程通信 一.实验目的 ​ 1.了解和熟悉Linux支持的消 ...

  4. 【Linux】网络编程三:TCP通信和UDP通信介绍及代码编写

    参考连接:https://www.nowcoder.com/study/live/504/2/16. [Linux]网络编程一:网络结构模式.MAC/IP/端口.网络模型.协议及网络通信过程简单介绍 ...

  5. php三要素,并发编程三要素:原子性,有序性,可见性

    并发编程三要素 **原子性:**一个不可再被分割的颗粒.原子性指的是一个或多个操作要么全部执行成功要么全部执行失败. 有序性: 程序执行的顺序按照代码的先后顺序执行.(处理器可能会对指令进行重排序) ...

  6. 并发操作之——并发编程三要素

    并发操作 并发操作之--并发编程三要素. 并发操作之--并发编程三要素 并发操作 前言 一.原子性 二.有序性: 三.可见性: 总结 前言 并发操作之--并发编程三要素. 一.原子性 一个不可再被分割 ...

  7. Python3 与 C# 并发编程之~ 进程篇

    上次说了很多Linux下进程相关知识,这边不再复述,下面来说说Python的并发编程,如有错误欢迎提出- 如果遇到听不懂的可以看上一次的文章:https://www.cnblogs.com/dotne ...

  8. 十四丶并发编程(线程 进程 协程)

    Yuan先生 知识预览 操作系统 回到顶部 操作系统 一 为什么要有操作系统? 现代计算机系统是由一个或者多个处理器,主存,磁盘,打印机,键盘,鼠标显示器,网络接口以及各种其他输入输出设备组成的复杂系 ...

  9. 并发编程之多进程进程进程

    Python 并发编程之多进程 1.1 multiprocessing 模块 Python 中的多线程无法利用多核资源,如果想要充分的使用多核 cpu 的资源,在 Python 中大部分情况需要使用多 ...

最新文章

  1. plsql 查询存储过程死锁语句_SQL2005查看死锁存储过程sp_who_lock
  2. Javascript Array对象
  3. apache虚拟机配置步骤和修改访问端口
  4. 服务器可以pyqt显示吗,用pyqt+socket实现远程操作服务器的一个例子,PyQtsocket,方法,示例...
  5. [Python图像处理] 九.形态学之图像开运算、闭运算、梯度运算
  6. python制作的游戏如何转化为swf_PYTHON实现swf提取
  7. Emacs之快捷键大全
  8. IOS系统定时APP
  9. 基于私有云的数据库高可用架构实践
  10. 快速导入Maven依赖的方法
  11. 产品经理的高阶能力:架构图的设计与画法
  12. 关于MD5和salt盐值加密后破解方法
  13. java动态二维数组定义_Java动态定义二维数组问题
  14. Java Attach机制简介
  15. 采集微信公众号数据的思路
  16. MD数据公式格式总结
  17. 第一次使用linux电脑,计算机基础与Linux初次亲密接触第一天(马哥)
  18. pycharm2018安装教程 pycharm2018永久激活教程
  19. 计算机技能大赛简讯内,科技节现场类比赛简讯
  20. 初识micro:bit

热门文章

  1. 【山外笔记-SVN命令】svn命令详解
  2. pycharm如何回到过去某个时间
  3. Ubuntu14.04和16.04官方默认更新源sources.list和第三方源推荐(干货!)
  4. 百度地图坐标点上图,行政区域轮廓的应用
  5. 顶尖电子秤ls6恢复出厂_顶尖电子称怎么恢复出厂默认?
  6. SpringBoot微信点餐开源系统
  7. nRF24L01芯片(模块)介绍
  8. WordPress内核 模板下载源码论坛全站源码带会员下载+DUX主题
  9. Git从本地向TFS push项目
  10. 超级浏览器的特色功能