进程间通信的方式有很多:匿名管道,有名管道,win32 WM_COPYDATA, 共享内存,消息队列,本地起socket ,或者本地起websockt。等等。
问题背景:windows c++程序和 QT已支持跨平台的项目,做进程间通信传输数据。
QT项目接洽了electron,electron不支持管道通信,所以管道排除,因为跨平台,win32的WM_COPYDATA也排除。
想要约定为标注输入输出做为通信方式,对应到win32这边,就是匿名管道通过创建子进程的方式重定向IO,但是匿名管道在win32平台是阻塞的,无法改为非阻塞,影响主程序的执行,这和linux很不同,linux下匿名管道可以设置为非阻塞,匿名管道排除。
为了改为非阻塞,使用有名管道,看微软官方文档,说有名管道也可以重定向子进程IO,但是本人愚钝,多次尝试未能成功通信。有名管道客户端测需要设置管道状态为非阻塞,非阻塞才能生效。有名管道+重定向子进程IO 目前失败。
最后没办法,使用本地起websocket作为通信方式。
匿名管道
使用重定向输入和输出创建子进程
这个案列可以参考微软给的demo,思路很清晰。
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
//1. 第一步,创建匿名管道
void MyCreatePipe() {
//使用匿名管道实现父子进程通信
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
ErrorExit(TEXT("StdoutRd CreatePipe"));
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdout SetHandleInformation"));
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
ErrorExit(TEXT("Stdin CreatePipe"));
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdin SetHandleInformation"));
}
//2. 创建子进程
void CreateChildProcess(const std::wstring sCmd)
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
{
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// 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 and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
siStartInfo.wShowWindow = SW_HIDE;
// Create the child process.
bSuccess = CreateProcess(NULL,
(LPWSTR)sCmd.c_str(), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
ErrorExit(TEXT("CreateProcess"));
else
{
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
// Close handles to the stdin and stdout pipes no longer needed by the child process.
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
CloseHandle(g_hChildStd_OUT_Wr);
CloseHandle(g_hChildStd_IN_Rd);
}
}
//3. 写管道
bool WriteToPipe(const std::string& sMsg)
{
DWORD dwWritten;
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, sMsg.c_str(), sMsg.size(), &dwWritten, NULL);
if (!bSuccess) return false;
// Close the pipe handle so the child process stops reading.
if (!CloseHandle(g_hChildStd_IN_Wr))
ErrorExit(TEXT("StdInWr CloseHandle"));
return true;
}
//4. 读管道
void ReadFromPipe(std::string& sReadedMsg)
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT.
// Stop when there is no more data.
{
sReadedMsg = "";
DWORD dwRead;
char szBuf[2048] = { 0 };
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
while (ReadFile(g_hChildStd_OUT_Rd, szBuf, sizeof(szBuf), &dwRead, NULL))
{
sReadedMsg += szBuf;
}
}
有名管道
上述方式我测试不通。
下面的重叠IO,有名管道服务器和客户端我测试可以。客户端设置管道为非阻塞。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#include <iostream>
#define CONNECTING_STATE 0
#define READING_STATE 1
#define WRITING_STATE 2
#define INSTANCES 4
#define PIPE_TIMEOUT 5000
#define BUFSIZE 4096
typedef struct
{
OVERLAPPED oOverlap;
HANDLE hPipeInst;
TCHAR chRequest[BUFSIZE];
DWORD cbRead;
TCHAR chReply[BUFSIZE];
DWORD cbToWrite;
DWORD dwState;
BOOL fPendingIO;
} PIPEINST, * LPPIPEINST;
VOID DisconnectAndReconnect(DWORD);
BOOL ConnectToNewClient(HANDLE, LPOVERLAPPED);
VOID GetAnswerToRequest(LPPIPEINST);
std::string ws2s(const std::wstring& ws);
void CreateChildProcess();
PIPEINST Pipe[INSTANCES];//0 IN-red; 1 IN-write; 2 OUT-red; 3 OUT-write
HANDLE hEvents[INSTANCES];
int _tmain(VOID)
{
DWORD i, dwWait, cbRet, dwErr;
BOOL fSuccess;
LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe");
// The initial loop creates several instances of a named pipe
// along with an event object for each instance. An
// overlapped ConnectNamedPipe operation is started for
// each instance.
for (i = 0; i < INSTANCES; i++)
{
// Create an event object for this instance.
hEvents[i] = CreateEvent(
NULL, // default security attribute
TRUE, // manual-reset event
TRUE, // initial state = signaled
NULL); // unnamed event object
if (hEvents[i] == NULL)
{
//printf("CreateEvent failed with %d.\n", GetLastError());
return 0;
}
Pipe[i].oOverlap.hEvent = hEvents[i];
Pipe[i].oOverlap.Offset = 0;
Pipe[i].oOverlap.OffsetHigh = 0;
std::wstring sPipeName = lpszPipename;
Pipe[i].hPipeInst = CreateNamedPipe(
sPipeName.c_str(), // pipe name
PIPE_ACCESS_DUPLEX | // read/write access
FILE_FLAG_OVERLAPPED, // overlapped mode
PIPE_TYPE_MESSAGE | // message-type pipe
PIPE_READMODE_MESSAGE | // message-read mode
PIPE_WAIT, // blocking mode
INSTANCES, // number of instances
BUFSIZE * sizeof(TCHAR), // output buffer size
BUFSIZE * sizeof(TCHAR), // input buffer size
PIPE_TIMEOUT, // client time-out
NULL); // default security attributes
if (Pipe[i].hPipeInst == INVALID_HANDLE_VALUE)
{
//printf("CreateNamedPipe failed with %d.\n", GetLastError());
return 0;
}
// Call the subroutine to connect to the new client
Pipe[i].fPendingIO = ConnectToNewClient(
Pipe[i].hPipeInst,
&Pipe[i].oOverlap);
Pipe[i].dwState = Pipe[i].fPendingIO ?
CONNECTING_STATE : // still connecting
READING_STATE; // ready to read
}
//CreateChildProcess();
while (1)
{
// Wait for the event object to be signaled, indicating
// completion of an overlapped read, write, or
// connect operation.
dwWait = WaitForMultipleObjects(
INSTANCES, // number of event objects
hEvents, // array of event objects
FALSE, // does not wait for all
INFINITE); // waits indefinitely
// dwWait shows which pipe completed the operation.
i = dwWait - WAIT_OBJECT_0; // determines which pipe
if (i < 0 || i >(INSTANCES - 1))
{
//printf("Index out of range.\n");
return 0;
}
// Get the result if the operation was pending.
if (Pipe[i].fPendingIO)
{
fSuccess = GetOverlappedResult(
Pipe[i].hPipeInst, // handle to pipe
&Pipe[i].oOverlap, // OVERLAPPED structure
&cbRet, // bytes transferred
FALSE); // do not wait
switch (Pipe[i].dwState)
{
// Pending connect operation
case CONNECTING_STATE:
if (!fSuccess)
{
//printf("Error %d.\n", GetLastError());
return 0;
}
Pipe[i].dwState = READING_STATE;
break;
// Pending read operation
case READING_STATE:
if (!fSuccess || cbRet == 0)
{
DisconnectAndReconnect(i);
continue;
}
Pipe[i].cbRead = cbRet;
Pipe[i].dwState = WRITING_STATE;
break;
// Pending write operation
case WRITING_STATE:
if (!fSuccess || cbRet != Pipe[i].cbToWrite)
{
DisconnectAndReconnect(i);
continue;
}
Pipe[i].dwState = READING_STATE;
break;
default:
{
//printf("Invalid pipe state.\n");
return 0;
}
}
}
// The pipe state determines which operation to do next.
switch (Pipe[i].dwState)
{
// READING_STATE:
// The pipe instance is connected to the client
// and is ready to read a request from the client.
case READING_STATE:
Pipe[i].cbRead = 0;
fSuccess = ReadFile(
Pipe[i].hPipeInst,
Pipe[i].chRequest,
BUFSIZE * sizeof(TCHAR),
&Pipe[i].cbRead,
&Pipe[i].oOverlap);
// The read operation completed successfully.
if (fSuccess && Pipe[i].cbRead != 0)
{
Pipe[i].fPendingIO = FALSE;
Pipe[i].dwState = WRITING_STATE;
continue;
}
// The read operation is still pending.
dwErr = GetLastError();
if (!fSuccess && (dwErr == ERROR_IO_PENDING))
{
Pipe[i].fPendingIO = TRUE;
continue;
}
// An error occurred; disconnect from the client.
DisconnectAndReconnect(i);
break;
// WRITING_STATE:
// The request was successfully read from the client.
// Get the reply data and write it to the client.
case WRITING_STATE:
GetAnswerToRequest(&Pipe[i]);
fSuccess = WriteFile(
Pipe[i].hPipeInst,
Pipe[i].chReply,
Pipe[i].cbToWrite,
&cbRet,
&Pipe[i].oOverlap);
// The write operation completed successfully.
if (fSuccess && cbRet == Pipe[i].cbToWrite)
{
Pipe[i].fPendingIO = FALSE;
Pipe[i].dwState = READING_STATE;
std::cout << "[" << Pipe[i].hPipeInst << "] 响应客户端:" << ws2s(Pipe[i].chReply) << std::endl;
continue;
}
// The write operation is still pending.
dwErr = GetLastError();
if (!fSuccess && (dwErr == ERROR_IO_PENDING))
{
Pipe[i].fPendingIO = TRUE;
continue;
}
// An error occurred; disconnect from the client.
DisconnectAndReconnect(i);
break;
default:
{
//printf("Invalid pipe state.\n");
return 0;
}
}
}
return 0;
}
// DisconnectAndReconnect(DWORD)
// This function is called when an error occurs or when the client
// closes its handle to the pipe. Disconnect from this client, then
// call ConnectNamedPipe to wait for another client to connect.
VOID DisconnectAndReconnect(DWORD i)
{
// Disconnect the pipe instance.
if (!DisconnectNamedPipe(Pipe[i].hPipeInst))
{
//printf("DisconnectNamedPipe failed with %d.\n", GetLastError());
}
// Call a subroutine to connect to the new client.
Pipe[i].fPendingIO = ConnectToNewClient(
Pipe[i].hPipeInst,
&Pipe[i].oOverlap);
Pipe[i].dwState = Pipe[i].fPendingIO ?
CONNECTING_STATE : // still connecting
READING_STATE; // ready to read
}
// ConnectToNewClient(HANDLE, LPOVERLAPPED)
// This function is called to start an overlapped connect operation.
// It returns TRUE if an operation is pending or FALSE if the
// connection has been completed.
BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo)
{
BOOL fConnected, fPendingIO = FALSE;
// Start an overlapped connection for this pipe instance.
fConnected = ConnectNamedPipe(hPipe, lpo);
// Overlapped ConnectNamedPipe should return zero.
if (fConnected)
{
//printf("ConnectNamedPipe failed with %d.\n", GetLastError());
return 0;
}
switch (GetLastError())
{
// The overlapped connection in progress.
case ERROR_IO_PENDING:
fPendingIO = TRUE;
break;
// Client is already connected, so signal an event.
case ERROR_PIPE_CONNECTED:
if (SetEvent(lpo->hEvent))
break;
// If an error occurs during the connect operation...
default:
{
//printf("ConnectNamedPipe failed with %d.\n", GetLastError());
return 0;
}
}
return fPendingIO;
}
VOID GetAnswerToRequest(LPPIPEINST pipe)
{
std::wstring sMsg = L"------Default answer from server---- - Default answer from server Defaul\
swer from server Default answer from serverDefault answer from serverDefault answer from server Default \
Hello Message answer from server Default answer from serverDefault answer from serverDefault \
answer from server Default answer from server Default answer from serverDefault answer from serverDefault \
from serverDefault answer from serverDefault answer from serverDefault answer from serverDefault answer from serverDefault answer from";
std::cout << "[" << pipe->hPipeInst << "] 接收到客户端消息:" << ws2s(pipe->chRequest) << std::endl;
StringCchCopy(pipe->chReply, sMsg.size(), sMsg.c_str());
pipe->cbToWrite = sMsg.size();
}
std::string ws2s(const std::wstring& ws)
{
std::string curLocale = setlocale(LC_ALL, NULL); // curLocale = "C";
setlocale(LC_ALL, "chs");
const wchar_t* _Source = ws.c_str();
size_t _Dsize = 2 * ws.size() + 1;
char* _Dest = new char[_Dsize];
memset(_Dest, 0, _Dsize);
wcstombs(_Dest, _Source, _Dsize);
std::string result = _Dest;
delete[]_Dest;
setlocale(LC_ALL, curLocale.c_str());
return result;
}
void CreateChildProcess()
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
{
TCHAR szCmdline[] = TEXT("D:\\xxx\\ConsoleApplication1\\Release\\TestPipe\\TestPipe.exe");
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// 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 and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = Pipe[1].hPipeInst;
siStartInfo.hStdOutput = Pipe[1].hPipeInst;
siStartInfo.hStdInput = Pipe[0].hPipeInst;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES ;
siStartInfo.wShowWindow = SW_SHOW;
// Create the child process.
bSuccess = CreateProcess(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
return;
else
{
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
// Close handles to the stdin and stdout pipes no longer needed by the child process.
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
//CloseHandle(g_hChildStd_OUT_Wr);
//CloseHandle(g_hChildStd_IN_Rd);
}
}
客户端步骤:
在win32种,因为有名管道可以是非血缘关系的进程,还可以是跨域的不通电脑设备,因此,管道名称的保存位置就很重要,方便各个客户端连接同一个服务端,管道名称可以写在注册表里面等。
- 创建文件(文件名是有名管道名称)
- 等待连接有名管道
- 设置管道状态特性
- 读写管道(业务操作)
//客户端侧,有名管道测试代码
// TestPipe.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#if 0 //匿名管道客户端
#include <windows.h>
#include <iostream>
#include <string>
int main()
{
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
char szBuf[2048] = { 0 };
DWORD nReaded = 0, nWrited = 0;
std::string sReaded(""),sDefSendMsg(" TestPipe 发送给服务端Pipe的数据!\n"), sWrited("");
BOOL bSuccess(FALSE);
FILE* fp = fopen("d://xxx//TestPipe.txt", "a+");
if (!fp)
return -1;
for (int i = 0; i < 10; i++)
{
//Sleep(1000 * 10);
ZeroMemory(szBuf, 2048);
sReaded = "第";
sReaded += std::to_string(i + 1);
sReaded += "个接收到服务端数据: ";
ReadFile(hStdIn, szBuf, 2048, &nReaded, nullptr);
sReaded += szBuf;
sReaded += "\n";
//::CloseHandle(hStdIn);
fwrite(sReaded.c_str(), 1, sReaded.size(), fp);
sWrited = "第";
sWrited += std::to_string(i + 1);
sWrited += sDefSendMsg;
bSuccess = WriteFile(hStdOut, sWrited.c_str(), sWrited.size(), &nWrited, nullptr);
if (!bSuccess)
return -1;
fwrite(sWrited.c_str(), 1, sWrited.size(), fp);
//::CloseHandle(hStdOut);
//getchar();
fclose(fp);
}
fclose(fp);
CloseHandle(hStdIn);
CloseHandle(hStdOut);
return 0;
}
#else //有名管道客户端
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#include <string>
#include <thread>
#define BUFSIZE 1024
std::string ws2s(const std::wstring& ws);
int _tmain(int argc, TCHAR* argv[])
{
HANDLE hPipe;
std::wstring sDefaultMsg = L"Default message from client.";
std::wstring sWriteMsg = L"";
std::wstring sReadedMsg = L"";
TCHAR chBuf[BUFSIZE];
BOOL fSuccess = FALSE;
DWORD cbRead, cbToWrite, cbWritten, dwMode;
LPTSTR lpszPipename = (LPTSTR)L"\\\\.\\pipe\\mynamedpipe";
if (argc > 1)
sWriteMsg = argv[1];
// Try to open a named pipe; wait for it, if necessary.
while (1)
{
hPipe = CreateFile(
lpszPipename, // pipe name
GENERIC_READ | // read and write access
GENERIC_WRITE,
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
// Break if the pipe handle is valid.
if (hPipe != INVALID_HANDLE_VALUE)
break;
// Exit if an error other than ERROR_PIPE_BUSY occurs.
if (GetLastError() != ERROR_PIPE_BUSY)
{
_tprintf(TEXT("Could not open pipe. GLE=%d\n"), GetLastError());
return -1;
}
// All pipe instances are busy, so wait for 20 seconds.
if (!WaitNamedPipe(lpszPipename, 20000))
{
printf("Could not open pipe: 20 second wait timed out.");
return -1;
}
}
// The pipe connected; change to message-read mode.
dwMode = PIPE_READMODE_MESSAGE | PIPE_NOWAIT; //设置管道为非阻塞才生效
fSuccess = SetNamedPipeHandleState(
hPipe, // pipe handle
&dwMode, // new pipe mode
NULL, // don't set maximum bytes
NULL); // don't set maximum time
if (!fSuccess)
{
_tprintf(TEXT("SetNamedPipeHandleState failed. GLE=%d\n"), GetLastError());
return -1;
}
//有名管道设置为非阻塞后,多线程也可以正常读取,不阻塞。有数据就读出来,没数据就立马返回。
for (int i = 0; i < 10; i++)
{
// Send a message to the pipe server.
std::cout << "------第" << i + 1 << "次开始发送给服务器 Server Pipe" << std::endl;
sWriteMsg = std::to_wstring(i + 1) + L" " + sDefaultMsg;
fSuccess = WriteFile(
hPipe, // pipe handle
sWriteMsg.c_str(), // message
sWriteMsg.size() + 1, // message length
&cbWritten, // bytes written
NULL); // not overlapped
if (!fSuccess)
{
_tprintf(TEXT("WriteFile to pipe failed. GLE=%d\n"), GetLastError());
return -1;
}
std::cout << __FUNCTION__ << " 发送成功:" << ws2s(sWriteMsg) << std::endl;
std::thread th([&]() {
// Read from the pipe.
ZeroMemory(chBuf, sizeof(chBuf));
sReadedMsg = L"";
while (ReadFile(
hPipe, // pipe handle
chBuf, // buffer to receive reply
BUFSIZE * sizeof(TCHAR), // size of buffer
&cbRead, // number of bytes read
NULL) > 0) // not overlapped
{
sReadedMsg += chBuf;
if (sReadedMsg.find(L"Hello Message"))
{
if (WriteFile(hPipe,
"******** Hi,It's m e ******",
27,
&cbRead,
nullptr) > 0)
{
std::cout << "Find Sucess!\n";
}
}
if (cbRead <= 0)
break;
}
std::cout << __FUNCTION__ << " 接收成功:" << ws2s(sReadedMsg) << std::endl;
});
th.detach();
getchar();
}
CloseHandle(hPipe);
getchar();
return 0;
}
std::string ws2s(const std::wstring& ws)
{
std::string curLocale = setlocale(LC_ALL, NULL); // curLocale = "C";
setlocale(LC_ALL, "chs");
const wchar_t* _Source = ws.c_str();
size_t _Dsize = 2 * ws.size() + 1;
char* _Dest = new char[_Dsize];
memset(_Dest, 0, _Dsize);
wcstombs(_Dest, _Source, _Dsize);
std::string result = _Dest;
delete[]_Dest;
setlocale(LC_ALL, curLocale.c_str());
return result;
}
#endif