当前位置: 首页 > backend >正文

Windows 匿名管道通信

Windows 匿名管道通信(Anonymous Pipe)实战教程

本文将系统讲解如何在 Windows 平台下,使用匿名管道实现进程间通信。通过逐步升级,我们将从最基本的收发通信,进阶到循环读写,再到实现非阻塞键盘监听与 ESC 退出等,帮助你全面理解匿名管道的实际用法。


❓ 为什么是两个 main() 文件?

💡 这个问题非常好,其实是 Windows 和 Linux 在匿名管道父子进程通信机制上的差异导致了目前这种“两份代码”的写法。

✅ 原因:Windows 没有 fork(),无法像 Linux 那样复制进程

  • Linux 中,我们使用 fork() 创建子进程,子进程自动继承父进程的匿名管道文件描述符,所以只需一个 main(),用 fork() 分出父子逻辑即可。

    👉 所以 Linux 写法是:一个文件,一个进程分支(if (fork() == 0))

  • 而在 Windows 中,不能用 fork(),只能用 CreateProcess() 来启动一个“独立的可执行文件”作为子进程。

    👉 所以需要:

    1. 编写一个父进程程序(创建匿名管道并调用 CreateProcess
    2. 编写一个子进程程序(读取匿名管道)

🔁 因此在 Windows 下实现“父子进程通信”通常需要两个可执行程序(两个 main()),它们由 CreateProcess() 连接。


✅ 基本的父子进程通信

🎯 目标

  • 父进程向子进程发送一条消息
  • 子进程读取并输出这条消息

📁 项目结构

ipc_demo_windows/
├── anon_pipe_parent.cpp
├── anon_pipe_child.cpp

🛠️ 编译与运行

  1. 打开“开始菜单”,搜索并启动:

    x64 Native Tools Command Prompt for VS 2022
    

    在这里插入图片描述

  2. 在该命令行中运行:

    cd D:\WorkCode\Demo\ipc_demo_windows
    cl anon_pipe_parent.cpp /Fe:anon_pipe_parent.exe
    cl anon_pipe_child.cpp /Fe:anon_pipe_child.exe
    anon_pipe_parent.exe
    

    ps:修改控制台编码为 UTF-8(在控制台输入 chcp 65001

📄 anon_pipe_parent.cpp

#include <windows.h>
#include <iostream>int main() {// 设置控制台编码为 UTF-8SetConsoleOutputCP(CP_UTF8);// 匿名管道的读端和写端句柄HANDLE hRead, hWrite;// 设置安全属性,允许句柄被子进程继承SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };// 创建匿名管道,hRead为读端,hWrite为写端CreatePipe(&hRead, &hWrite, &sa, 0);STARTUPINFO si = { sizeof(STARTUPINFO) };  // 子进程启动信息结构体PROCESS_INFORMATION pi;                    // 子进程信息结构体char szCmd[] = "anon_pipe_child.exe";      // 要启动的子进程程序路径// 设置子进程的标准输入为管道读端si.dwFlags = STARTF_USESTDHANDLES;si.hStdInput = hRead;                            // 子进程从这个读端读取数据si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 保持子进程输出显示到当前控制台si.hStdError = GetStdHandle(STD_ERROR_HANDLE);   // 保持子进程错误输出显示到当前控制台// 启动子进程(读取端)CreateProcess(NULL,      // 应用程序名(可为 NULL)szCmd,     // 命令行字符串NULL, NULL,// 进程/线程安全属性(不需要)TRUE,      // 继承句柄,必须为 TRUE,否则子进程无法访问管道0,         // 创建标志NULL,      // 使用父进程环境变量NULL,      // 使用父进程工作目录&si,       // 启动信息&pi        // 返回的进程信息);CloseHandle(hRead); // 父进程不需要读,关闭读端// 父进程写入消息到管道(子进程会从读端接收)const char* msg = "Hello from parent process!\n";DWORD dwWritten;WriteFile(hWrite, msg, strlen(msg), &dwWritten, NULL);// 清理资源:关闭写端管道句柄和子进程句柄CloseHandle(hWrite);CloseHandle(pi.hProcess);CloseHandle(pi.hThread);return 0;
}

📄 anon_pipe_child.cpp

#include <windows.h>
#include <iostream>int main() {char buffer[128];     // 用于存放从管道读取的数据DWORD dwRead;         // 实际读取到的字节数// 从标准输入读取数据(父进程通过匿名管道写入)if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer) - 1, &dwRead, NULL)) {// 添加字符串终止符,确保输出不会超界或乱码buffer[dwRead] = '\0';// 打印接收到的消息std::cout << "子进程收到消息: " << buffer << std::endl;} else {std::cerr << "ReadFile failed." << std::endl;}return 0;
}

✅ 循环收发消息(阻塞读写)

🎯 目标

  • 父进程持续输入
  • 子进程持续读取
  • 输入 “exit” 结束通信

🔁 改进点

  • 使用循环 + std::getline() 持续输入
  • 关闭写端后,子进程自动结束读取

📄 父进程循环发送消息

#include <string>while (true) {std::getline(std::cin, msg);if (msg == "exit") break;msg += "\n";WriteFile(hWrite, msg.c_str(), msg.length(), &dwWritten, NULL);
}
CloseHandle(hWrite);

📄 子进程循环读取消息

while (true) {BOOL bSuccess = ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)-1, &dwRead, NULL);if (!bSuccess || dwRead == 0) break;buffer[dwRead] = '\0';std::cout << "收到消息: " << buffer;
}

✅ 模拟非阻塞输入 + ESC 键退出

🎯 目标

  • 支持 ESC 键随时退出
  • 回车发送消息
  • 支持退格键删除

📄 父进程增强版输入逻辑

#include <conio.h>std::string inputBuffer;
while (true) {// 检查是否有键盘按键按下(非阻塞)if (_kbhit()) {char ch = _getch();  // 读取按下的字符(不显示到屏幕)if (ch == 27) {// ESC 键退出循环(ASCII 27)break;} else if (ch == '\r') {// 回车键被按下,表示用户输入完成一行std::cout << "\n";  // 回显换行// 构造消息并通过匿名管道写入子进程std::string msgToSend = inputBuffer + "\n";  // 添加换行符WriteFile(hWrite, msgToSend.c_str(), msgToSend.length(), &dwWritten, NULL);// 清空缓冲区,准备下一次输入inputBuffer.clear();} else if (ch == '\b') {// 用户按下退格键if (!inputBuffer.empty()) {inputBuffer.pop_back();	// 从缓冲区移除最后一个字符std::cout << "\b \b";	// 清除控制台上最后一字符(回退一格、覆盖空格、再回退)}} else {// 正常字符输入,追加到缓冲区并回显inputBuffer += ch;std::cout << ch;}}Sleep(10);  // 避免 CPU 占用过高,休眠 10ms
}

✅ 双向通信(全双工)

🎯 目标

  • 父进程:从键盘输入发送消息给子进程,并接收子进程回传消息
  • 子进程:读取父进程发送的内容,并回传响应信息
  • 双向通信:两个匿名管道实现两个方向的消息传递

🗂️ 架构设计图

            父进程                                子进程┌────────┐                          ┌────────────┐输入 →│ Write A│ ─── 匿名管道 A ──▶───▶──│ Read A     ││        │                          │ 处理消息    ││ Read B │◀── 匿名管道 B ◀───◀────│ Write B    │└────────┘                          └────────────┘↑ 打印响应                        ↑ 输入响应

📄 父进程

#include <windows.h>
#include <iostream>
#include <string>int main() {// 设置控制台编码为 UTF-8SetConsoleOutputCP(CP_UTF8);HANDLE hParentWriteA, hChildReadA; // 父写 子读HANDLE hChildWriteB, hParentReadB; // 子写 父读SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE };// Pipe A:父写,子读CreatePipe(&hChildReadA, &hParentWriteA, &sa, 0);// Pipe B:子写,父读CreatePipe(&hParentReadB, &hChildWriteB, &sa, 0);// 禁止父进程继承子进程的读/写端句柄SetHandleInformation(hParentWriteA, HANDLE_FLAG_INHERIT, 0);SetHandleInformation(hParentReadB, HANDLE_FLAG_INHERIT, 0);// 启动子进程STARTUPINFO si = { sizeof(si) };PROCESS_INFORMATION pi;si.dwFlags = STARTF_USESTDHANDLES;si.hStdInput = hChildReadA;si.hStdOutput = hChildWriteB;si.hStdError = GetStdHandle(STD_ERROR_HANDLE);char cmd[] = "anon_pipe_child.exe";if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {std::cerr << "CreateProcess failed\n";return 1;}CloseHandle(hChildReadA);CloseHandle(hChildWriteB);std::string input;char buffer[256];DWORD dwRead, dwWritten;std::cout << "输入消息(输入 exit 退出):\n";while (true) {std::getline(std::cin, input);if (input == "exit") break;input += "\n";WriteFile(hParentWriteA, input.c_str(), input.size(), &dwWritten, NULL);// 等待子进程回传if (ReadFile(hParentReadB, buffer, sizeof(buffer) - 1, &dwRead, NULL) && dwRead > 0) {buffer[dwRead] = '\0';std::cout << "子进程回应: " << buffer;}}CloseHandle(hParentWriteA);CloseHandle(hParentReadB);CloseHandle(pi.hProcess);CloseHandle(pi.hThread);return 0;
}

📄 子进程

#include <windows.h>
#include <iostream>
#include <string>int main() {char buffer[256];DWORD dwRead, dwWritten;while (true) {BOOL b = ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer) - 1, &dwRead, NULL);if (!b || dwRead == 0) break;buffer[dwRead] = '\0';std::string response = std::string("收到: ") + buffer;// 回传给父进程,通过 STD_OUTPUT_HANDLE(即 Pipe B)WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), response.c_str(), response.size(), &dwWritten, NULL);}return 0;
}

✅ 子进程存活检测(心跳检测)

🎯 目标

  • 父进程每隔一段时间检测子进程是否还活着
  • 如果子进程崩溃、退出、异常关闭 → 父进程能检测到并终止通信
  • 防止 ReadFile 阻塞、写入失败导致父进程“挂死”

📄 交互触发检测

以下是在原来父进程基础上增加子进程活跃检测的代码片段。

while (true) {// 检查子进程是否仍然活着DWORD waitResult = WaitForSingleObject(pi.hProcess, 0);if (waitResult == WAIT_OBJECT_0) {std::cout << "[警告] 子进程已退出,终止通信。\n";break;}std::getline(std::cin, input);if (input == "exit") break;input += "\n";WriteFile(hParentWriteA, input.c_str(), input.size(), &dwWritten, NULL);// 读取子进程回应if (ReadFile(hParentReadB, buffer, sizeof(buffer)-1, &dwRead, NULL) && dwRead > 0) {buffer[dwRead] = '\0';std::cout << "子进程回应: " << buffer;}Sleep(50);  // 降低 CPU 占用
}

📄 定时检测

#include <windows.h>
#include <iostream>
#include <string>
#include <thread>
#include <atomic>std::atomic_bool g_bRunning = true;// 子进程存活监控线程函数
void MonitorChildProcess(HANDLE hChildProcess) {while (g_bRunning) {DWORD waitResult = WaitForSingleObject(hChildProcess, 0);if (waitResult == WAIT_OBJECT_0) {std::cout << "\n[监控] 子进程已退出,终止通信。\n";g_bRunning = false;break;}std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 每200ms检测一次}
}int main() {HANDLE hParentWriteA, hChildReadA; // 父写 子读HANDLE hChildWriteB, hParentReadB; // 子写 父读SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE };// Pipe A:父写,子读CreatePipe(&hChildReadA, &hParentWriteA, &sa, 0);// Pipe B:子写,父读CreatePipe(&hParentReadB, &hChildWriteB, &sa, 0);// 禁止父进程继承这些不需要的句柄SetHandleInformation(hParentWriteA, HANDLE_FLAG_INHERIT, 0);SetHandleInformation(hParentReadB, HANDLE_FLAG_INHERIT, 0);STARTUPINFO si = { sizeof(si) };PROCESS_INFORMATION pi;si.dwFlags = STARTF_USESTDHANDLES;si.hStdInput = hChildReadA;si.hStdOutput = hChildWriteB;si.hStdError = GetStdHandle(STD_ERROR_HANDLE);char cmd[] = "anon_pipe_child.exe";if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {std::cerr << "CreateProcess failed\n";return 1;}CloseHandle(hChildReadA);CloseHandle(hChildWriteB);// 启动子进程存活检测线程std::thread monitorThread(MonitorChildProcess, pi.hProcess);std::string input;char buffer[256];DWORD dwRead, dwWritten;std::cout << "输入消息(输入 exit 退出):\n";while (g_bRunning) {std::getline(std::cin, input);if (input == "exit") break;input += "\n";WriteFile(hParentWriteA, input.c_str(), input.size(), &dwWritten, NULL);if (ReadFile(hParentReadB, buffer, sizeof(buffer) - 1, &dwRead, NULL) && dwRead > 0) {buffer[dwRead] = '\0';std::cout << "子进程回应: " << buffer;}}g_bRunning = false;if (monitorThread.joinable()) monitorThread.join();CloseHandle(hParentWriteA);CloseHandle(hParentReadB);CloseHandle(pi.hProcess);CloseHandle(pi.hThread);return 0;
}

❌ 设置子进程的“父进程属性”

🎯 目标

  • 任务管理器中显示“可折叠的父子进程结构”

🧠 关键技术点

  • 使用 STARTUPINFOEX 代替普通的 STARTUPINFO
  • 使用 InitializeProcThreadAttributeListUpdateProcThreadAttribute
  • 设置属性 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
  • CreateProcess 中使用 EXTENDED_STARTUPINFO_PRESENT 标志

📄 父进程

#include <windows.h>
#include <iostream>
#include <string>int main() {// 创建匿名管道 A(父写,子读) 和 B(子写,父读)HANDLE hParentWriteA, hChildReadA;HANDLE hChildWriteB, hParentReadB;SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };CreatePipe(&hChildReadA, &hParentWriteA, &sa, 0);CreatePipe(&hParentReadB, &hChildWriteB, &sa, 0);SetHandleInformation(hParentWriteA, HANDLE_FLAG_INHERIT, 0);SetHandleInformation(hParentReadB, HANDLE_FLAG_INHERIT, 0);// 构造 STARTUPINFOEX + 属性列表(设置可折叠父子进程关系)STARTUPINFOEXA siex = {};siex.StartupInfo.cb = sizeof(STARTUPINFOEXA);siex.StartupInfo.dwFlags = STARTF_USESTDHANDLES;siex.StartupInfo.hStdInput  = hChildReadA;siex.StartupInfo.hStdOutput = hChildWriteB;siex.StartupInfo.hStdError  = GetStdHandle(STD_ERROR_HANDLE);SIZE_T attrListSize = 0;InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);LPPROC_THREAD_ATTRIBUTE_LIST lpAttrList =(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrListSize);InitializeProcThreadAttributeList(lpAttrList, 1, 0, &attrListSize);HANDLE hRealParent = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());UpdateProcThreadAttribute(lpAttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hRealParent, sizeof(HANDLE), NULL, NULL);siex.lpAttributeList = lpAttrList;// 创建子进程(支持可折叠显示)PROCESS_INFORMATION pi;char cmd[] = "anon_pipe_child.exe";if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &siex.StartupInfo, &pi)) {std::cerr << "CreateProcess failed: " << GetLastError() << "\n";return 1;}// 创建 Job 对象以确保任务管理器识别父子绑定关系HANDLE hJob = CreateJobObject(NULL, NULL);AssignProcessToJobObject(hJob, pi.hProcess);// 关闭子进程用的管道句柄CloseHandle(hChildReadA);CloseHandle(hChildWriteB);// 循环消息交互(写入 Pipe A,读取 Pipe B)std::string input;char buffer[256];DWORD dwRead, dwWritten;std::cout << "输入消息(输入 exit 退出):\n";while (true) {std::getline(std::cin, input);if (input == "exit") break;input += "\n";WriteFile(hParentWriteA, input.c_str(), input.size(), &dwWritten, NULL);if (ReadFile(hParentReadB, buffer, sizeof(buffer) - 1, &dwRead, NULL) && dwRead > 0) {buffer[dwRead] = '\0';std::cout << buffer;}}// 清理资源CloseHandle(hParentWriteA);CloseHandle(hParentReadB);CloseHandle(pi.hProcess);CloseHandle(pi.hThread);CloseHandle(hRealParent);CloseHandle(hJob);DeleteProcThreadAttributeList(lpAttrList);HeapFree(GetProcessHeap(), 0, lpAttrList);return 0;
}

问题:没有在任务管理器显示折叠


📌 总结

通过上面实现,我们完成了从基础收发到灵活控制的匿名管道通信开发。

http://www.xdnf.cn/news/3259.html

相关文章:

  • 自尊量表(SES)在线测试:探索你的自我价值认知
  • AI智能体 | 使用Coze制作提取单条抖音文案并二创
  • 百家号等新媒体私信入口是否可以聚合到企业微信的客服,如何实现
  • Nginx — http、server、location模块下配置相同策略优先级问题
  • 【AI提示词】二八法则专家
  • 【今日探针卡行业分析】2025年4月30日
  • 在Electron中爬取CSDN首页的文章信息
  • 【神经网络与深度学习】探索全连接网络如何学习数据的复杂模式,提取高层次特征
  • 无水印短视频素材下载网站有哪些?十个高清无水印视频素材网站分享
  • vue2 el-element中el-select选中值,数据已经改变但选择框中不显示值,需要其他输入框输入值才显示这个选择框才会显示刚才选中的值
  • 【自然语言处理与大模型】大模型意图识别实操
  • 【MCP Node.js SDK 全栈进阶指南】高级篇(6):MCP服务大规模部署方案
  • 分享5款让电脑更方便更有趣的软件
  • 树的序列化 - 学习笔记
  • 聚焦数字中国|AI赋能与安全守护:Coremail引领邮件办公智能化转型
  • DeepSeek最新大模型发布-DeepSeek-Prover-V2-671B
  • Depth Anything V2:深度万象 V2
  • 【Prometheus-OracleDB Exporter安装配置指南,开机自启】
  • buildroot 和 busybox 系统的优缺点
  • 科普--- 云中心的概念
  • DeepSeek-V3 解读,第一部分:理解 Multi-Head Latent Attention
  • redis-单节点-主从节点-哨兵模式
  • webrtc ICE 打洞总结
  • 【网络原理】 《TCP/IP 协议深度剖析:从网络基础到协议核心》
  • 楼宇智能化一、二章【期末复习】
  • LeetCode —— 572. 另一棵树的子树
  • 【昇腾】Benchmark
  • 算法导论第6章思考题
  • linux find命令妙用
  • 公司运营-税务篇