1.问题阐述
命名管道是通过网络来完成进程间的通信的,它屏蔽了底层的网络协议细节。所以在不了解网络协议的情况下,也可以利用命名管道来实现进程间的通信。命名管道充分利用了Windows NT和Windows 2000内建的安全机制。命名管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统(Named Pipe File System,NPFS)”接口。将命名管道作为一种网络编程方案时,它实际上建立了一个客户机/服务器通信体系,并在其中可靠地传输数据。创建管道的进程称为管道服务器,连接到一个管道的进程称为管道客户机。管道服务器和一台或多台管道客户机进行单向或双向的通信。一个命名管道的所有实例共享同一个管道名,但是每一个实例均拥有独立的缓存与句柄,并且为客户——服务通信提供一个分离的管道。实例的使用保证了多个管道客户能够在同一时间使用同一个命名管道。
2.实现技巧
命名管道提供了两种基本通信模式:字节模式和消息模式。在字节模式中,数据以一个连续的字节流的形式,在客户机和服务器之间流动。而在消息模式中,客户机和服务器则通过一系列不连续的数据单位,进行数据的收发,每次在管道上发出了一条消息后,它必须作为一条完整的消息读入。
由于命名管道采用“命名管道文件系统(Named Pipe File System,NPFS)”接口,因此,客户机和服务器可利用标准的Win32文件系统函数(例如ReadFile和WriteFile)来进行数据的收发,创建命名管道CreateNamedPipe的原型如下:
HANDLE CreateNamedPipe( LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
|
其中,lpName为管道的名字,格式为//./pipe/pipename,名字的最大长度为256,管道名字对字符的大小写不敏感。
dwPipeMode是管道的打开模式,等待客户端连接函数ConnectNamedPipe的原型如下:
BOOL ConnectNamedPipe( HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped );
|
等待命名管道函数WaitNamedPipe的原型如下:
BOOL WaitNamedPipe( LPCTSTR lpNamedPipeName, DWORD nTimeOut );
|
读取数据和写入数据的函数原型分别如下。
1)读取数据
BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped ); |
2)写入数据
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ); |
3.实例代码
下面给出创建共享命名管道的部分关键代码。
1)服务器端
void CNamedPipeSrvView::OnPipeCreate() { // TODO: 在此添加代码 hPipe=CreateNamedPipe(".//pipe//MyPipe", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0,1,1024,1024,0,NULL); if(INVALID_HANDLE_VALUE==hPipe) { MessageBox("创建命名管道失败!"); hPipe=NULL; return; } HANDLE hEvent; hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent) { MessageBox("创建事件对象失败!"); CloseHandle(hPipe); hPipe=NULL; return; } OVERLAPPED ovlap; ZeroMemory(&ovlap,sizeof(OVERLAPPED)); ovlap.hEvent=hEvent; if(!ConnectNamedPipe(hPipe,&ovlap)) { if(ERROR_IO_PENDING!=GetLastError()) { MessageBox("等待客户端连接失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } } if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE)) { MessageBox("等待对象失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } CloseHandle(hEvent); } void CNamedPipeSrvView::OnPipeRead() { // TODO: 在此添加相关代码 char buf[100]; DWORD dwRead; if(!ReadFile(hPipe,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); } void CNamedPipeSrvView::OnPipeWrite() { // TODO: 在此添加相关代码 char buf[]="http://www.163.com"; DWORD dwWrite; if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } } |
2)客户端
void CNamedPipeCltView::OnPipeConnect() { // TODO: 在此添加相关代码 if(!WaitNamedPipe(".//pipe//MyPipe",NMPWAIT_WAIT_FOREVER)) { MessageBox("当前没有可利用的命名管道实例!"); return; } hPipe=CreateFile(".//pipe//MyPipe",GENERIC_READ | GENERIC_WRITE, 0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hPipe) { MessageBox("打开命名管道失败!"); hPipe=NULL; return; } } void CNamedPipeCltView::OnPipeRead() { // TODO: 在此添加相关代码 char buf[100]; DWORD dwRead; if(!ReadFile(hPipe,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } MessageBox(buf); } void CNamedPipeCltView::OnPipeWrite() { // TODO: 在此添加相关代码 char buf[]="命名管道测试程序"; DWORD dwWrite; if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; } } |
4.小结
命名管道服务器和客户机的区别在于:服务器是唯一一个有权创建命名管道的进程,也只有它才能接收管道客户机的连接请求,而客户机只能同一个现成的命名管道服务器建立连接。命名管道服务器只能在Windows NT或Windows 2000上创建,所以,我们无法在两台Windows 95或Windows 98计算机之间利用管道进行通信。不过,客户机可以是Windows 95或Windows 98计算机,与Windows NT或Windows 2000计算机进行连接通信。