基于多进程并发-进程通讯之管道(pipe)

news2025/1/12 8:42:31

一、管道(pipe)

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

特性:

  • 有两种类型的管道:匿名管道,有名管道(也叫命名管道)
  • 简单实现
  • 有大小限制
  • 无格式的字节流数据
  • 用户态与内核态之间数据拷贝
  • 匿名管道:单向通信、不可跨PC
  • 有名管道:双向通信、可跨PC
  • 自带同步与互斥
  • 内核里面的⼀串缓存(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; //管道大小
)

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);

参数含义
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;
}
  • 子进程
// 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;
}

5、结果

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

三、有名管道

在这里插入图片描述

1、简介

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

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

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

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

  • 服务器
    命名管道服务器,步骤如下:
    1)使用API函数CreateNamedPipe,创建一个命名管道实例句柄。
    2)使用API函数ConnectNamedPipe,在命名管道实例上监听客户机连接请求。
    3)分别使用ReadFile和WriteFile这两个API函数,从客户机接收数据,或将数据发给客户机。
    4)使用API函数DisconnectNamedPipe,关闭命名管道连接。
    5)使用API函数CloseHandle,关闭命名管道实例句柄。
  • 客户端
    命名管道客户机,步骤如下:
    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, 必须为这种格式。中间的“.”表示本地机器,如果要跟远程机器建立连接,则需要设定远程服务器的名字。

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

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

3.3、等候一个命名管道实例WaitNamedPipe
等到超时间隔过或指定命名管道的实例可用于连接

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

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 connected
	if (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 5


int 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、输出

在这里插入图片描述

如有错误或不足欢迎评论指出!创作不易,转载请注明出处。如有帮助,记得点赞关注哦(⊙o⊙)
更多内容请关注个人博客:https://blog.csdn.net/qq_43148810

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windwos2016 由于没有远程桌面授权服务器可以提供许可证

一、问题&#xff1a; 经常会遇到&#xff0c;server2016、server2012、server2008操作系统&#xff0c;安装远程桌面服务之后没有激活&#xff0c;经过120天到期之后&#xff0c;没办法再使用&#xff0c;重新安装激活远程桌面服务也不能用。 二、具体的报错如下图&#xff…

归并排序详解-附Python代码

排序思路 将输入的列表递归分解成若干个有序的子列表&#xff08;只含有一个元素&#xff09;&#xff1b;将分解后的有序子列表两两归并成一个新的有序列表&#xff1b;重复步骤2&#xff0c;直到完成排序。 重点&#xff1a;如何定义一个归并函数&#xff0c;可以将两个有序…

Qt-自定义控件

Qt-自定义控件 简单使用 首先创建一个工程 在现有的工程上添加文件&#xff0c;选择Qt设计师界面类 选择Widget 添加两个控件之后&#xff0c;选择水平布局 将刚刚自定义的控件smallWidget放置在原始的控件中 首先在原始工程的ui界面 随便放置一个widget 选择&#xff…

我的内网渗透-代理转发(1)

概念 网关 必须经过 用来进行路由转发的设备&#xff0c;网关的作用是让不同网段之间能够通信 代理 委托访问 无论代理后面挂了几台设备&#xff0c;都认为是从代理进行访问&#xff0c;对外只表现为代理一台。外部认为是与代理进行…

计算机提示xinput1_3.dll丢失,三个详细修复方法

打开《绝地求生》游戏的时候&#xff0c;计算机提示xinput1_3.dll丢失&#xff0c;无法启动运行。重新安装一遍游戏依然无法启动运行。这个是由于xinput1_3.dll文件是属于电脑系统DirectX9.0的一个组件&#xff0c;用于提供输入和输出功能。它包含了各种接口和函数&#xff0c;…

spi控制器和spi设备的加载过程

spi控制器都是挂在platform总线上的&#xff0c;所以要等platform总线上的设备驱动加载spi控制器完成后才能加载spi设备。 1.spi控制器加载 由spi控制器驱动程序调用spi_register_master来完成spi控制器驱动加载 int spi_register_master(struct spi_master *master) { ... s…

【ubuntu20.04上构建qemu启动linux kernel】

参考Ubuntu环境下使用qemu搭建arm64运行环境 - 简书 一、交叉编译工具 sudo apt install gcc-aarch64-linux-gnu aarch64-linux-gnu-gcc -v 二、linux内核编译 git clone https://github.com/torvalds/linux.git cp arch/arm64/configs/virt.config .config make ARCHarm64 m…

并网逆变器杂记1-VO-DCC双环控制

NOTE1&#xff1a; 母线电压恒定的条件是&#xff1a; PV输出功率 &#xff0c;等于逆变侧消耗功率 假设&#xff1a;PV侧给母线输入10A &#xff0c;但是逆变侧消耗1A&#xff0c;此时母线Udc会升高 反之PV输入1A&#xff0c;消耗10A&#xff0c;母线Udc会降低。 NOTE2&#…

【IP地址】使用这个免费工具轻松获取地理位置

文章目录 前言一、Ip-API二、使用示例2.1、语言2.2、数据格式 三、简单示例四、更好的服务五、需要注意的点结尾 前言 今天分享一个免费的在线工具来查询IP地址所在的地理位置。可以通过IP地址所属的网络运营商和其他相关信息来确定设备的位置&#xff0c;包括国家、地区、城市…

CentOs中操作用户命令(添加或删除)

1、不添加任何参数,创建 zhangsan 用户 不加参数时,创建用户默认创建一个用户目录以及用户和组同名,且UID和GID相同 useradd zhangsan 用 id和 ll 命令查看一下,是否成功创建用户目录以及用户和用户组 id ll uid1003( zhangsan) gid1003( zhangsan) 组1003( zhangsan) 2…

Obsidian 与 Typora 图片兼容保存路径一致设置

目录 一、问题二、解决方法 Obsidian官网 &#xff1a; https://obsidian.md/ Your thoughts are yours. 你的想法就是你的。 Obsidian stores notes on your device, so you can access them quickly, even offline. No one else can read them, not even us. 黑曜石将笔记存储…

Java——基础语法

文章目录 1. 变量&#xff1a;变量的声明和初始化变量的作用域变量的命名规则常量 2. 运算符&#xff1a;算术运算符关系运算符逻辑运算符位运算符其他运算符 3. 流程控制&#xff1a;分支结构循环结构跳转控制 4. 类与对象&#xff1a;类的概念对象的概念类的成员构造方法和析…

深入理解深度学习——BERT派生模型:T5(Text to Text Transfer Transformer)

分类目录&#xff1a;《深入理解深度学习》总目录 T5的全称为Text to Text Transfer Transformer&#xff0c;是谷歌提出的预训练语言模型领域的通用模型&#xff0c;该模型将所有自然语言问题都转化成文本到文本的形式&#xff0c;并用一个统一的模型解决。为了得到大一统的高…

Tkinter创建列表使用方法

前言 ttk.Treeview 是 Tkinter 模块中的一个组件&#xff0c;它提供了一个可用于显示层次结构数据的树状列表。它具有以下特点和功能&#xff1a; 显示层次结构&#xff1a;ttk.Treeview支持以树状结构显示数据。每个节点可以有子节点&#xff0c;从而形成层次结构。用户可以展…

GPT-2源码实现及GPT-3、GPT-3.5、GPT-4及GPT-5内幕解析(三)

GPT-2源码实现及GPT-3、GPT-3.5、GPT-4及GPT-5内幕解析(三) 5.3 GPT-3 内幕机制可视化解析 GPT-3是一个基于Transformer的语言模型,通过不同的层次提取语言不同层面的特性,构建整个语言的语义信息,它学习的过程跟人类正常学习的过程是类似的,开始的时候是一个无监督预训练…

材料表面与界面 关键概念介绍

目录 1. Conductivity and two general modes of charge transport in solid-state materials (Fig. 1b) 2. What is Bravais lattice, what is basis and what is crystal lattice (Fig. 2). The differences between five possible Bravais lattices in two dimensions (Fi…

【计算机毕设系统项目说明】VB+ACCESS采购管理系统开发(论文+系统+答辩PPT+封面)

大家好&#xff0c;这里是小伙整理的VB项目系列&#xff0c;大家可以用于自己的课设Q或毕设&#xff0c;需要的可自取。 项目包含内容 项目包含&#xff1a; 项目源码 论文 答辩PPT 其他相关 截图&#xff1a; 库存管理软件说明 一.系统设置 1&#xff09; 锁定&#xff1…

2023考研一战上岸 电子科技大学 860软件工程 经验分享

目录 1. 前言&#xff1a;考研&#xff0c;心态最重要&#xff01; 2. 初试各科复习经验 (1) 数学一 (2) 英语一 (3) 专业课 (4) 政治 (5) 四门课时间划分 3. 复试流程和备考建议 (1) 复试流程 (2) 备考建议 4. 结语 首先&#xff0c;先简要做一个自我介绍&#xff…

记录:Qt Creator 10配置安卓开发环境

Qt Creator 现在的安卓开发环境配置相比老版本方便了不少&#xff0c;本文以目前在线安装版的 Qt Creator 10.0.2 Qt 5.15 / Qt 6.5 为例做演示&#xff08;有些文件可能会因为网络问题需要科学上网才能下载&#xff09;。 1.下载 JDK 11 https://adoptium.net/zh-CN/temuri…

MATLAB 之 对话框设计

这里写目录标题 一、对话框设计1. 控件的种类及作用2. 控件的操作2.1 建立控件对象2.2 控件对象的基本控制属性2.3 建立控件对象举例 一、对话框设计 对话框是用户与计算机进行信息交流的临时窗口&#xff0c;在现代软件中有着广泛的应用。在软件设计时&#xff0c;借助于对话…