总体概述
最近几月基本在windows下编程,对于一直在Linux系统开发的需要花时间学习windows下的编码特性,比如IPC通信,Linux一般常用Unix domain socket,转到windows,对应则是namedpipe编码。本章即为入门介绍windows的namedpipe通信。
备注:windows在win10的17063 build开始实验室引入AF_UNIX,详情见windows devblogs。
管道编码API
CreateNamedPipeA:服务端使用,创建命名管道的实例,并返回后续管道操作的句柄。主要注意(1)参数lpName的命名格式\.\pipe\pipename,(2)参数dwPipeMode,PIPE_WAIT指定阻塞模式,对应PIPE_NOWAIT非阻塞模式,(3)参数nMaxInstances,一般设置PIPE_UNLIMITED_INSTANCES标识无限制,如果指定了个数,示例实测中客户端接入超限导致服务侧异常退出(return -1)。
ConnectNamedPipe:服务端使用,允许命名管道服务器进程监听等待客户端进程连接到命名管道的实例。
createFileA:客户端使用,创建或者打开文件或 I/O 设备,本文里用到打开命名管道。参数lpFileName与CreateNamedPipeA的一致,dwDesiredAccess为设置访问权限,GENERIC_READ表示读,GENERIC_WRITE标识写,可两者一同设置。
waitNamedPipeA:客户端使用,连接指定命名管道,超时则返回。对应错误信息通过GetLastError获取。
SetNamedPipeHandleState:客户端使用,设置读取和阻塞模式,与CreateNamedPipeA的dwPipeMode作用相似。
完整代码示例
服务端:
#include <windows.h>
#include <process.h>
#include <tchar.h>
#include <iostream>
#include <string>
#include <cstdlib>
#define BUFSIZE 2048
typedef struct ThreadParamS_
{
HANDLE pipe;
int *idx;
}ThreadParamS;
unsigned __stdcall MsgProcessThread ( void * pParam)
{
HANDLE hPipe = (HANDLE)(((ThreadParamS*)pParam)->pipe);
int *thrd = ((ThreadParamS*)pParam)->idx;
while(1)
{
char szBufRecv[1024] = {0};
DWORD dwReadSize = 0;
std::cout << "ready to read message..." << std::endl;
BOOL bRet = ::ReadFile(hPipe,szBufRecv,1024,&dwReadSize,NULL);
if(!bRet || dwReadSize == 0)
{
DWORD dwLastError = ::GetLastError();
if(dwLastError == ERROR_BROKEN_PIPE)
std::cout << "disconnected..." << std::endl;
else
std::cout << "ReadFile Error:"<<dwLastError << std::endl;
break;
}
else
{
std::cout << "server receive " << dwReadSize << " bytes: " << szBufRecv << std::endl;
std::string srouce_str,a1,a2,str_sum;
srouce_str = szBufRecv;
[srouce_str,&a1,&a2,&str_sum](){
auto pos_flag = srouce_str.find("+");
if(pos_flag != std::string::npos)
{
a1 = srouce_str.substr(0,pos_flag);
a2 = srouce_str.substr(pos_flag+1);
int add_value1 = atoi(a1.c_str());
int add_value2 = atoi(a2.c_str());
int sum = add_value1 + add_value2;
char szTemp[100];
_itoa_s(sum,szTemp,100,10);
str_sum = szTemp;
}
}();
DWORD dwWritten = 0;
bRet = WriteFile(hPipe,str_sum.c_str(),str_sum.length() + 1,&dwWritten,NULL);
if(!bRet)
{
int nError = ::GetLastError();
std::cout << "server call WriteFile fail, errorid:" << nError << std::endl;
break;
}
else if(dwWritten == 0)
{
std::cout << "server call WriteFile fail, The number of sent bytes is 0." << std::endl;
break;
}
}
}
CloseHandle(hPipe);
*thrd = 1;
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;
int thrds[2] = {1, 1};
const char * lpszPipename = ("\\\\.\\pipe\\namedpipe_20240606");
for (;;)
{
hPipe = CreateNamedPipeA( lpszPipename, PIPE_ACCESS_DUPLEX,PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUFSIZE,BUFSIZE,0,NULL);
if (hPipe == INVALID_HANDLE_VALUE)
{
std::cout << "CreateNamedPipeA fail:" << ::GetLastError() << std::endl;
return -1;
}
std::cout << "[main]The server is ready to receive the client connection..." << std::endl;
BOOL fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if(fConnected)
{
bool acc = false;
std::cout << "get client connected..." << std::endl;
for (int i = 0; i < 2;i++)
{
if (thrds[i] == 1)
{
acc = true;
std::cout << "accept client connected..." << std::endl;
thrds[i] = 0;
ThreadParamS p = {hPipe, &thrds[i]};
CloseHandle((HANDLE)_beginthreadex(NULL,0,MsgProcessThread,(void*)&p,0,NULL));
break;
}
}
if (!acc)
{
std::cout << "do not accept client connected!" << std::endl;
}
}
else
CloseHandle(hPipe);
}
system("puase");
return 0;
}
客户端:
#include <windows.h>
#include <tchar.h>
#include <ctime>
#include <cstdlib>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hPipe = []()->HANDLE
{
while(1)
{
HANDLE hPipe = CreateFileA( "\\\\.\\pipe\\namedpipe_20240606",GENERIC_READ | GENERIC_WRITE, 0,NULL,OPEN_EXISTING,0, NULL);
if(hPipe != INVALID_HANDLE_VALUE)
{
std::cout << "open pipe success!" << std::endl;
return hPipe;
}
int nErrorId = GetLastError();
if(nErrorId != ERROR_PIPE_BUSY)
{
std::cout << "client createfile error :" << nErrorId << std::endl;
return NULL;
}
std::cout << "WaitNamedPipeA ..." << std::endl;
if(!WaitNamedPipeA("\\\\.\\pipe\\namedpipe_20240606",10000))
{
if(GetLastError() == ERROR_SEM_TIMEOUT)
std::cout << "WaitNamePipeA timeOut!" << std::endl;
else
{
std::cout << "WaitNamePipeA Failed:" << GetLastError() << std::endl;
break;
}
}
else
{
std::cout << "waitNamedPipe success." << std::endl;
continue;
}
}
return NULL;
}();
if(hPipe == INVALID_HANDLE_VALUE || !hPipe)
{
std::cout << "connect server failed!" << std::endl;
system("pause");
return 0;
}
std::cout << "[client]connect server success." << std::endl;
DWORD dwMode = PIPE_READMODE_MESSAGE | PIPE_NOWAIT;
if(!SetNamedPipeHandleState(hPipe,&dwMode,NULL,NULL))
{
std::cout << "SetNamedPipeHandleState failed!" << std::endl;
system("pause");
return 0;
}
while (true)
{
char send_buff[256] = {0};
int a1 = rand() % 100;
int a2 = rand() % 100;
sprintf_s(send_buff,"%d+%d",a1,a2);
DWORD dwWritten = 0;
if(!WriteFile(hPipe,send_buff,strlen(send_buff)+1,&dwWritten,NULL))
{
int nLastError = ::GetLastError();
if(ERROR_NO_DATA == nLastError)
std::cout << "pipi already closeed!" << std::endl;
else
std::cout << "client writefile failed:" << nLastError << std::endl;
system("pause");
return 0;
}
if(dwWritten == 0)
{
std::cout << "client writefile failed dwWritten = 0" << std::endl;
system("pause");
return 0;
}
std::cout << "[client] write data to server success." << std::endl;
char buffer_readed[256] = {0};
DWORD dwReaded = 0;
Sleep(10);
if(!ReadFile(hPipe,buffer_readed,256,&dwReaded,NULL))
{
int nLastError = ::GetLastError();
if(ERROR_NO_DATA == nLastError)
std::cout << "pipi already closeed!" << std::endl;
else
std::cout << "client ReadFile failed:" << nLastError << std::endl;
system("pause");
return 0;
}
if(dwReaded == 0)
{
std::cout << "client ReadFile failed:dwReaded == 0" << std::endl;
system("pause");
return 0;
}
char szBuff[256] = {0};
int nSum = atoi(buffer_readed);
if(nSum != a1 + a2)
{
std::cout << "miscalculation!!!" << std::endl;
}
sprintf_s(szBuff,"%d+%d=%s",a1,a2,buffer_readed);
std::cout << szBuff << std::endl;
Sleep(2000);
}
return 0;
}
备注:代码示例,服务端通过数组限制线程数,客户端通过设置PIPE_NOWAIT避免服务端无法服务时永久阻塞等待。源码是参考网上进行的改造。
参考文献
[1] 博客园#windows named pipe 客户端 服务器#
[2] 微软#win32 API Namedpipeapi.h#