在Windows平台下,C++的管道(Pipeline)通信主要分为匿名管道(Anonymous Pipes)和命名管道(Named Pipes)两种,分别适用于父子进程和无关进程间的通信。以下从原理、实现到代码示例详细说明:
⚙️ 一、匿名管道(Anonymous Pipes)
适用场景:父子进程间的单向数据流(如重定向子进程输出)5。
核心步骤:
- 父进程调用
CreatePipe
创建读/写句柄。 - 通过
STARTUPINFO
将句柄传递给子进程。 - 子进程通过继承的句柄读写数据。
代码示例(父进程读取子进程输出)5:
#include <Windows.h>
#include <iostream>
int main() {
HANDLE hReadPipe, hWritePipe;
SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE }; // 允许句柄继承
CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
si.hStdOutput = hWritePipe; // 重定向子进程输出到管道写端
si.dwFlags = STARTF_USESTDHANDLES;
TCHAR cmd[] = TEXT("child.exe");
CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
CloseHandle(hWritePipe); // 父进程关闭写端
char buffer[4096];
DWORD bytesRead;
while (ReadFile(hReadPipe, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) {
std::cout.write(buffer, bytesRead); // 打印子进程输出
}
CloseHandle(hReadPipe);
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
return 0;
}
关键点:
- 子进程需将输出重定向到管道写端(
si.hStdOutput = hWritePipe
)。 - 父进程必须关闭未使用的句柄(如写端),否则读操作会阻塞5。
🔍 二、命名管道(Named Pipes)
适用场景:无关进程的双向通信(如C++服务端与C#客户端交互)1,6,7。
1. 同步通信示例(服务端-客户端)
服务端代码(创建管道并等待连接)2,3:
#include <Windows.h>
#include <iostream>
int main() {
// 创建命名管道(双向、消息模式)
HANDLE hPipe = CreateNamedPipe(
L"\\\\.\\pipe\\MyPipe", // 管道名称
PIPE_ACCESS_DUPLEX, // 双向通信
PIPE_TYPE_MESSAGE | PIPE_WAIT, // 消息模式 + 阻塞
PIPE_UNLIMITED_INSTANCES, // 最大实例数
4096, 4096, // 输入/输出缓冲区大小
0, NULL); // 默认超时和安全属性
if (hPipe == INVALID_HANDLE_VALUE) {
std::cerr << "CreateNamedPipe failed. Error: " << GetLastError() << std::endl;
return 1;
}
std::cout << "等待客户端连接..." << std::endl;
if (ConnectNamedPipe(hPipe, NULL)) { // 阻塞等待客户端
const char* msg = "Hello from Server!";
DWORD bytesWritten;
WriteFile(hPipe, msg, strlen(msg) + 1, &bytesWritten, NULL); // 发送数据
std::cout << "数据已发送" << std::endl;
}
CloseHandle(hPipe);
return 0;
}
客户端代码(连接管道并读取数据)2,4:
#include <Windows.h>
#include <iostream>
int main() {
if (WaitNamedPipe(L"\\\\.\\pipe\\MyPipe", NMPWAIT_WAIT_FOREVER)) { // 等待管道可用
HANDLE hPipe = CreateFile(
L"\\\\.\\pipe\\MyPipe", // 管道名称
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (hPipe != INVALID_HANDLE_VALUE) {
char buffer[256];
DWORD bytesRead;
ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL); // 读取数据
std::cout << "收到服务端消息: " << buffer << std::endl;
CloseHandle(hPipe);
}
}
return 0;
}
2. 异步通信(高性能场景)
使用OVERLAPPED
结构实现非阻塞I/O,避免线程阻塞9:
// 服务端异步读取示例
HANDLE hPipe = CreateNamedPipe(..., FILE_FLAG_OVERLAPPED, ...); // 启用异步模式
OVERLAPPED ov = {0};
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建事件对象
if (ConnectNamedPipe(hPipe, &ov)) { // 异步连接
DWORD bytesRead;
char buffer[256];
if (ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, &ov)) {
// 读取成功(可能立即完成)
} else if (GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(ov.hEvent, INFINITE); // 等待操作完成
}
}
CloseHandle(ov.hEvent);
优势:
- 通过事件对象(
hEvent
)通知I/O完成,提升并发性能9。 - 适合服务端处理多客户端请求的场景。
⚖️ 三、关键问题与解决方案
问题 | 原因 | 解决方案 |
---|---|---|
连接阻塞 | 客户端未启动或管道名称错误 | 使用WaitNamedPipe 检测管道可用性4 |
数据读写失败 | 缓冲区不足或模式不匹配(字节/消息) | 统一PIPE_TYPE_MESSAGE 和PIPE_READMODE_MESSAGE 3 |
句柄泄漏 | 未关闭管道或事件对象 | 确保CloseHandle 释放所有资源9 |
跨语言通信乱码 | 编码不一致(如C++窄字节 vs C# UTF-8) | 双方统一使用UTF-8 编码6,7 |
💎 四、总结与建议
-
选型原则:
-
最佳实践:
-
扩展场景: