管道
概念
管道是进程间通信使用的一种共享内存的方式,创建管道的进程称为管道服务器,连接管道的进程成为管道客户端。进程间通过管道方式,一个进程往管道写入数据,另一个进程从管道取数据,类似socket通信从buf缓冲区读写数据,管道操作也类似文件读写。管道分为匿名管道和命名管道,文中主要介绍命名管道操作。
匿名管道
匿名管道,没有名称,而且是单工模式,就是两个进程只能一个进程往管道写入数据,另一个进程往管道区数据,而且匿名管道只能使用在父进程与子进程之间,因此如果想要在局域网内使用管道通信,不能使用匿名管道。
命名管道
命名管道,有名称,服务器端有个进程负责创建一个有名的管道,并且可以接收客户端的连接请求,客户端只能连接给定的管道名称,命名管道提供两种通信模式,字节模式和消息模式。在字节模式中,数据以一个连续的字节流的形式在客户机和服务器之间流动。而在消息模式中,客户机和服务器则通过一系列不连续的数据单位进行数据的收发,每次在管道上发出一条消息后,它必须作为一条完整的消息读入。
服务器API接口
HANDLE CreateNamedPipeA(
LPCSTR lpName,
DWORD dwOpenMode,
DWORD dwPipeMode,
DWORD nMaxInstances,
DWORD nOutBufferSize,
DWORD nInBufferSize,
DWORD nDefaultTimeOut,
LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
lpName管道名称,格式为 “\\.\pipe\pipename”,pipename不区分大小写,如果是远程终端进程间通信,格式中的’.'可以替换成ip地址,此时需要降低进程访问的权限,否者客户端进程在与服务器进程连接时报用户名或密码错误,或者权限不够,错误码1326或者5.
dwOpenMode打开模式,指定管道访问模式,PIPE_ACCESS_DUPLEX客户端和服务器都能读写管道数据,PIPE_ACCESS_INBOUND客户端写,服务器读取数据,PIPE_ACCESS_OUTBOUND客户端读取,服务器写入数据。
dwPipeMode管道模式,分为字节模式和消息模式,这两种模式和实例模式,等待模式结合使用,如果需要指定是否需要远程客户端过来连接,模式参数为PIPE_ACCEPT_REMOTE_CLIENTS PIPE_REJECT_REMOTE_CLIENTS
nMaxInstances管道实例创建的最大数量,最大值为PIPE_UNLIMITED_INSTANCES,如果超过这个值,则返回INVALID_HANDLE_VALUE,
nOutBufferSize管道输出buffer的大小
nInBufferSize管道输入buffer的大小
nDefaultTimeOut默认超时时间,单位是毫秒,如果不设置,可以置为FILE_ATTRIBUTE_NORMAL
lpSecurityAttributes安全属性结构指针,默认设置为NULL,如果需要降低进程访问权限,需要设置安全属性结构。
BOOL ConnectNamedPipe(
HANDLE hNamedPipe,
LPOVERLAPPED lpOverlapped
);
等待客户端建立连接,类似TCP通信的accept函数。
hNamedPipe是管道句柄。
lpOverlapped是指向重叠结构的指针,设置为NULL。
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);
从管道缓冲buffer读取数据
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
向管道缓冲buffer写入数据
BOOL DisconnectNamedPipe(
HANDLE hNamedPipe
// handle to named pipe
);
断开客户端的连接
BOOL
WINAPI
CloseHandle(
_In_ _Post_ptr_invalid_ HANDLE hObject
);
关闭管道句柄
客户端API接口
BOOL WaitNamedPipeA(
LPCSTR lpNamedPipeName,
DWORD nTimeOut
);
判断命名管道是否存在并且可用,设置超时时间。
lpNamedPipeName命名管道名称,如果是本机环境,与服务器一致,如果是远程终端进程通信,需要把 "."换成远程服务器ip,格式 \\servername\pipe\pipename
nTimeOut超时时间。
HANDLE CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
CreateFileA类似tcp通信客户端的connect接口,与服务器通信。
lpFileName管道名称
dwDesiredAccess访问权限,一般设置读或写,或者读与写
dwShareMode共享模式,就是文件被打开时,其它进程或者线程是否可以操作,例如共享读写,如果设置为NULL,其它进程只能等到文件句柄关闭时才可以操作。
lpSecurityAttributes安全属性结构指针,设置为NULL
dwCreationDisposition文件是否存在,一般设置OPEN_EXISTING
dwFlagsAndAttributes一般设置FILE_ATTRIBUTE_NORMAL
hTemplateFile如果打开一个存在的文件,此参数被忽略,一般设置NULL
BOOL SetNamedPipeHandleState(
HANDLE hNamedPipe,
LPDWORD lpMode,
LPDWORD lpMaxCollectionCount,
LPDWORD lpCollectDataTimeout
);
对命名管道设置读写模式和阻塞模式。
读写数据同上
服务器端示例代码
#include <windows.h>
#include <iostream>
using namespace std;
#define BUFSIZE 4096
#pragma comment(lib, "Advapi32.lib")
int main()
{
int nRecv, nSend;
DWORD dwBytes = 0;
char cRecv[BUFSIZE] = { 0x00 };
char cSend[BUFSIZE] = {0x00};
HANDLE hNamedPipe;
LPSTR lpsPipeName = (char*)"\\\\.\\pipe\\testNamePipe";
BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH];
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = &sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, (PACL)0, FALSE);
hNamedPipe = CreateNamedPipeA(
lpsPipeName,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT| PIPE_ACCEPT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES,
BUFSIZE,
BUFSIZE,
FILE_ATTRIBUTE_NORMAL,
&sa
);
if (hNamedPipe == INVALID_HANDLE_VALUE)
{
cout << "create pipe failed.\n";
return 0;
}
cout << "hNamedPipe value:" << hNamedPipe << endl;
bool bConnect = ConnectNamedPipe(hNamedPipe, NULL);
if (bConnect)
{
memset(cRecv, 0x00, sizeof(char)*BUFSIZE);
bool bRet = ReadFile(
hNamedPipe,
cRecv,
BUFSIZE*sizeof(char),
&dwBytes,
NULL
);
if (bRet)
{
cout<< "recv data len:" << dwBytes << endl;
cout << "recv data: "<<cRecv << endl;
}
else
{
cout << "recv data failed" << endl;
}
memset(cSend, 0x00, sizeof(char)*BUFSIZE);
strcpy_s(cSend, "test pipe ok");
bRet = WriteFile(hNamedPipe, cSend, strlen(cSend), &dwBytes, NULL);
if (!bRet || dwBytes != strlen(cSend))
{
FlushFileBuffers(hNamedPipe);
DisconnectNamedPipe(hNamedPipe);
CloseHandle(hNamedPipe);
}
}
else
{
CloseHandle(hNamedPipe);
}
return 1;
}
客户端示例代码
#include <windows.h>
#include <iostream>
using namespace std;
#define BUFSIZE 4096
int main()
{
HANDLE hNamedPipe;
int nRecv, nSend;
DWORD dwBytes = 0, dwMode;
char cRecv[BUFSIZE] = { 0x00 };
char cSend[BUFSIZE] = { 0x00 };
LPSTR lpsPipeName = (char*)"\\\\192.168.6.50\\pipe\\testNamePipe";
if (!WaitNamedPipeA(lpsPipeName, 20000))
{
cout << "can not open pipe" << GetLastError() << endl;
return 0;
}
hNamedPipe = CreateFileA(
lpsPipeName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hNamedPipe == INVALID_HANDLE_VALUE)
{
cout << "hNamedPipe is not valid." << GetLastError() << endl;
return 0;
}
if (GetLastError() == ERROR_PIPE_BUSY)
{
cout << "can not open pipe, busy" << endl;
return 0;
}
cout << "hNamedPipe value:" << hNamedPipe << endl;
dwMode = PIPE_READMODE_MESSAGE| PIPE_ACCEPT_REMOTE_CLIENTS;
bool bRet = SetNamedPipeHandleState(hNamedPipe, &dwMode, NULL, NULL);
if (!bRet)
{
cout << "SetNamedPipeHandleState failed." << endl;
return 0;
}
strcpy_s(cSend, "test pipe communication.");
bRet = WriteFile(hNamedPipe, cSend, strlen(cSend), &dwBytes, NULL);
if (!bRet)
{
cout << "WriteFile failed. GetLastError() " << GetLastError() << endl;
return 0;
}
bRet = ReadFile(hNamedPipe, cRecv, BUFSIZE*sizeof(char), &dwBytes, NULL);
if (!bRet)
{
cout << "ReadFile failed. GetLastError() " << GetLastError() << endl;
return 0;
}
CloseHandle(hNamedPipe);
return 1;
}