使用命名管道
管道服务器首次调用CreateNamedPipe()函数时,使用nMaxInstance参数指定了能同时存在的管道实例的最大数目。服务器可以重复调用CreateNamedPipe()函数去创建管道新的实例,直至达到设定的最大实例数。下面给出CreateNamedPipe()的函数原型:
HANDLE CreateNamedPipe( LPCTSTR lpName, // 指向管道名称的指针 DWORD dwOpenMode, // 管道打开模式 DWORD dwPipeMode, // 管道模式 DWORD nMaxInstances, // 最大实例数 DWORD nOutBufferSize, // 输出缓存大小 DWORD nInBufferSize, // 输入缓存大小 DWORD nDefaultTimeOut, // 超时设置 LPSECURITY_ATTRIBUTES lpSecurityAttributes // 安全属性指针 ); |
如果在已定义超时值变为零以前,有一个实例管道可以使用,则创建成功并返回管道句柄,以此侦听来自客户机的连接请求。另一方面,客户机通过函数WaitNamedPipe()使服务器进程等待来自客户的实例连接。如果在超时值变为零以前,有一个管道可供连接使用,则函数将成功返回,并通过调用CreateFile()或CallNamedPipe()来呼叫对服务器的连接。此时服务器将接受客户的连接请求,成功建立连接,服务器调用的等待客户机建立连接的ConnectNamedPipe()函数也将成功返回。
从调用时序上看,首先是客户机通过WaitNamedPipe()使服务器的CreateFile()在限时时间内创建实例成功,然后双方通过ConnectNamedPipe()和CreateFile()成功连接,在返回用以通信的文件句柄后,客户、服务双方即可进行通信。
在建立了连接后,客户机与服务器即可通过ReadFile()和WriteFile()并利用得到的管道句柄,以文件读写的形式彼此间进行信息交换。 当客户与服务器的通信结束,或是由于某种原因一方需要断开时,由客户机调用CloseFile()函数关闭打开的管道句柄,服务器随即调用DisconnectNamedPipe()函数。当然,服务器也可以通过单方面调用DisconnectNamedPipe()来终止连接。在终止连接后调用函数CloseHandle()来关闭此管道。下面给出的程序清单即是按照上述方法实现的命名管道服务器和客户机进行通信的简单程序实现代码: 服务器端:
m_hPipe = CreateNamedPipe("\\\\.\\Pipe\\Test", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 1000, NULL); // 创建命名管道 if (m_hPipe == INVALID_HANDLE_VALUE) m_sMessage = "创建命名管道失败!"; else{ m_sMessage = "成功创建命名管道!"; AfxBeginThread(ReadProc, this); // 开启线程 } |
由于ConnectNamedPipe()函数在没有客户机连接到服务器时会无限等待下去,因此为避免由此引起主线程的阻塞,为其开辟了一个子线程ReadProc:
UINT ReadProc(LPVOID lpVoid) { char buffer[1024]; // 数据缓存 DWORD ReadNum; CServerView* pView = (CServerView*)lpVoid; // 获取视句柄 if (ConnectNamedPipe(pView->m_hPipe, NULL) == FALSE) // 等待客户机的连接 { CloseHandle(pView->m_hPipe); // 关闭管道句柄 pView->m_sMessage = "与客户机建立连接失败!"; // 显示信息 pView->Invalidate(); return 0; }else{ pView->m_sMessage = "与客户机建立连接!"; // 显示信息 pView->Invalidate(); } // 从管道读取数据 if (ReadFile(pView->m_hPipe, buffer, sizeof(buffer), &ReadNum, NULL) == FALSE) { CloseHandle(pView->m_hPipe); // 关闭管道句柄 pView->m_sMessage = "从管道读取数据失败!"; // 显示信息 pView->Invalidate(); } else { buffer[ReadNum] = '\0'; // 显示接收到的信息 pView->m_sMessage = CString(buffer); pView->Invalidate(); } return 1; } |
在客户同服务器建立连接后,ConnectNamedPipe()才会返回,其下语句才得以执行。随后的ReadFile()将负责把客户写入管道的数据读取出来。在全部操作完成后,服务器可以通过调用函数DisconnectNamedPipe()而终止连接:
if (DisconnectNamedPipe(m_hPipe) == FALSE) // 终止连接 m_sMessage = "终止连接失败!"; else { CloseHandle(m_hPipe); // 关闭管道句柄 m_sMessage = "成功终止连接!"; }
|
客户机端:
CString Message = "[测试数据,由客户机发出]"; // 要发送的数据 DWORD WriteNum; // 发送的是数据长度 // 等待与服务器的连接 if (WaitNamedPipe("\\\\.\\Pipe\\Test", NMPWAIT_WAIT_FOREVER) == FALSE) { m_sMessage = "等待连接失败!"; // 显示信息 Invalidate(); return; } // 打开已创建的管道句柄 HANDLE hPipe = CreateFile("\\\\.\\Pipe\\Test", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPipe == INVALID_HANDLE_VALUE) { m_sMessage = "管道打开失败!"; // 显示信息 Invalidate(); return; } else { m_sMessage = "成功打开管道!"; // 显示信息 Invalidate(); } // 向管道写入数据 if (WriteFile(hPipe, Message, Message.GetLength(), &WriteNum, NULL) == FALSE) { m_sMessage = "数据写入管道失败!"; // 显示信息 Invalidate(); } else { m_sMessage = "数据成功写入管道!"; // 显示信息 Invalidate(); } CloseHandle(hPipe); // 关闭管道句柄 |
|