管道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。通常我们把管道分为匿名管道和命名管道。但对于匿名管道的话,只能在本机上进程之间通信,而且只能实现本地的父子进程之间的通信,局限性太大了。而这里介绍的命名管道,就和匿名管道有些不同了,在功能上也就显得强大许多,至少其可以实现跨网络之间的进程的通信,同时其客户端既可以接收数据也可以发送数据,服务器端也是可以接收数据,又可以发送数据。
命名管道(NamedPipes)是在管道服务器和一台或多台管道客户机之间进行单向或双向通信的一种命名的管道。一个命名管道的所有实例共享同一个管道名,但是每一个实例均拥有独立的缓存与句柄,并且为客户——服务通信提供有一个分离的管道。实例的使用保证了多个管道客户能够在同一时间使用同一个命名管道。
学习补充资料:孙鑫MFC笔记教程(17)--进程间通信2(命名管道)
https://blog.csdn.net/liufei_learning/article/details/5060281
命名管道的使用步骤
服务器端:
1):服务器进程调用CreateNamedPipe函数来创建一个有名称的命名管道在创建命名管道的时候必须指定一个本地的命名管道名称。windows允许同一个本地的命名管道名称右多个命名管道实例。所以,服务器进程在调用CreateNamedPipe函数时必须指定最大允许的实例数(0-255).如果CreateNamedPipe函数成功返回后,服务器进程得到一个指向一个命名管道实例的句柄。
HANDLE CreateNamedPipe(
LPCTSTR lpName, // pipe name
DWORD dwOpenMode, // pipe open mode
DWORD dwPipeMode, // pipe-specific modes
DWORD nMaxInstances, // maximum number of instances
DWORD nOutBufferSize, // output buffer size
DWORD nInBufferSize, // input buffer size
DWORD nDefaultTimeOut, // time-out interval
LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD
);
该函数用来创建一个命名管道的实例,并返回这个命名管道的句柄。如果需要创建一个命名管道的多个实例,就需要多次调用CreateNamedPipe函数,参数 lpName 为一个字符串,其格式必须为 \\.\pipe\pipeName,其中圆点 ”.” 表示的是本地机器,如果想要与远程的服务器建立连接,那么这个圆点位置处应指定这个远程服务器的名称,而其中的 “pipe” 这个是个固定的字符串,也就是说不能进行改变的,最后的 “pipename” 则代表的是我将要创建的命名管道的名称了,参数 dwOpenMode 用来指定管道的访问方式,重叠方式,写直通方式,还有管道句柄的安全访问方式。
2):服务器进程就可以调用ConnectNamedPipe来等待客户的连接请求,这个ConnectNamedPipe既支持同步形式,又支持异步形式,若服务器进程以同步形式调用 ConnectNamedPipe函数,如果没有得到客户端的连接请求,则会一直等到客户端的连接请求。当该函数返回时,客户端和服务器之间的命名管道连接已经建立起来了
BOOL ConnectNamedPipe(
HANDLE hNamedPipe, // handle to named pipe
LPOVERLAPPED lpOverlapped // overlapped structure
);
该函数的作用是让服务器等待客户端的连接请求的到来 hNamedPipe 指向一个命名管道实例的服务器的句柄
pOverlapped 指向一个 OVERLAPPED结构的指针,如果 hNamedPipe 所标识的命名管道是用 FILE_FLAG_OVERLAPPED,
(也就是重叠模式或者说异步方式)标记打开的,则这个参数不能为 NULL ,必须是一个有效的指向一个 OVERLAPPED 结构的指针,否则该函数可能会错误的执行。
3):这个时候服务器端就可以向客户端读(ReadFile)/写(WriteFile)数据了。
BOOL ReadFile(
HANDLE hFile, // handle to file
LPVOID lpBuffer, // data buffer
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // number of bytes read
LPOVERLAPPED lpOverlapped // overlapped buffer
);
4):在已经建立连接的命名管道实例中,服务器进程就会得到一个指向该管道实例的句柄,这个句柄称之为服务器端句柄,同时服务端进程可以调用DisconnectNamedPipe函数,将一个管道实例与当前建立连接的客户端进程断开,从而可以重新连接到新的客户端进程。当然,服务器也可以调用CloseHandle来关闭一个已经建立连接的命名管道实例。
BOOL DisconnectNamedPipe( HANDLE hNamedPipe // handle to named pipe);
服务器端程序:
#include <windows.h>
#include <stdio.h>
void main(void)
{
HANDLE PipeHandle;
DWORD BytesRead;
CHAR buffer[1024] = {0};
if ((PipeHandle = CreateNamedPipe("\\\\.\\Pipe\\mypipe",PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE,1,0,0,1000,NULL)) == INVALID_HANDLE_VALUE)
{
printf("CreateNamedPipe failed with error %x \n",GetLastError());
return;
}
printf("Server is now running \n");
if (ConnectNamedPipe(PipeHandle,NULL) == 0)
{
printf("ConnectNamePipe failed with error %x \n",GetLastError());
CloseHandle(PipeHandle);
return;
}
printf("ConnectNamedPipe成功!\n");
if(ReadFile(PipeHandle,buffer,sizeof(buffer),&BytesRead,NULL) <= 0)
{
printf("ReadFile failed with error %x \n",GetLastError());
CloseHandle(PipeHandle);
return;
}
printf("byteread = %d,buffer = %s \n",BytesRead,buffer);
if (DisconnectNamedPipe(PipeHandle) == 0)
{
printf("DisconnectNamedPipe failed with error %x \n",GetLastError());
return;
}
CloseHandle(PipeHandle);
system("pause");
}
客户端:
1):在这里客户端也可以先调用WaitNamedPipe函数来测试指定名称的管道实例是否可用。在已经建立的命名管道实例中,客户端进程就会得到一个指向该管道实例的句柄。这个句柄称之为客户端句柄。
BOOL WaitNamedPipe(
LPCTSTR lpNamedPipeName, // pipe name
DWORD nTimeOut // time-out interval
);
通过该函数可以判断是否有可用的命名管道。直到等待的时间间隔已过,或者指定的命名管道的实例可以用来连接了。lpNamedPipeName 用来指定命名管道的名称,格式同CreateNamedPipe函数的lpNamedPipeName参数。
nTimeOut 用来指定超时间隔,参数可以填写系列的值::
NMPWAIT_USE_DEFAULT_WAIT::超时间隔即为服务器端创建该命名管道时指定的超时间隔。NMPWAIT_USE_DEFAULT_WAIT::一直等待,直到出现一个可用的命名管道的实例。
2):客户端进程调用CreateFile函数连接到一个正在等待连接的命名管道上。在这里客户端需要指定将要连接的命名管道上。当CreateFile成功返回之后,客户端就得到了一个指向已经建立连接的命名管道实例的句柄。
HANDLE CreateFile(
LPCTSTR lpFileName, // file name
DWORD dwDesiredAccess, // access mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to template file
);
3):这个时候客户端就可以向服务器读(ReadFile)/写(WriteFile)数据了.
BOOL WriteFile(
HANDLE hFile, // handle to file
LPCVOID lpBuffer, // data buffer
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPDWORD lpNumberOfBytesWritten, // number of bytes written
LPOVERLAPPED lpOverlapped // overlapped buffer
);
4):客户端可以调用CloseHandle来关闭一个已经建立连接的命名管道实例。
BOOL CloseHandle( HANDLE hObject // handle to object);
客户端程序:
#include <windows.h>
#include <stdio.h>
#include <string>
using namespace std;
void main(void)
{
HANDLE PipeHandle;
DWORD BytesWritten;
char abc[20] = "This is a test!";
if (WaitNamedPipe("\\\\.\\Pipe\\mypipe", NMPWAIT_WAIT_FOREVER) == 0)
{
printf("CreateNamedPipe failed with error %x \n",GetLastError());
return;
}
printf("Waitnamedpipe正确!\n");
if ((PipeHandle = CreateFile("\\\\.\\Pipe\\mypipe",GENERIC_READ | GENERIC_WRITE,
0 ,(LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL)) == INVALID_HANDLE_VALUE)
{
printf("CreateFile failed with error %x \n",GetLastError());
return;
}
printf("CreateFile成功!\n");
if(WriteFile(PipeHandle,abc, 25, &BytesWritten, NULL) == 0)
{
printf("WriteFile failed with error %x \n",GetLastError());
CloseHandle(PipeHandle);
return;
}
printf("WriteFile成功!\n");
printf("Wrote %d bytes \n", BytesWritten);
CloseHandle(PipeHandle);
system("pause");
}
运行结果(VC++6.0):