导航

使用命名管道实现IPC

Posted on 2010-02-08 09:28  hcfalan  阅读(417)  评论(0编辑  收藏  举报
命名管道具有以下几个特征:
(1)命名管道是双向的,所以两个进程可以通过同一管道进行交互。
(2)命名管道不但可以面向字节流,还可以面向消息,所以读取进程可以读取写进程发送的不同长度的消息。
(3)多个独立的管道实例可以用一个名称来命名。例如几个客户端可以使用名称相同的管道与同一个服务器进行并发通信。
(4)命名管道可以用于网络间两个进程的通信,而其实现的过程与本地进程通信完全一致。
实验目标:在客户端输入数据a和b,然后发送到服务器并计算a+b,然后把计算结果发送到客户端。可以多个客户端与同一个服务器并行通信。
界面设计:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer';this.onclick=function(){window.open('http://bbs.pediy.com/upload/2006/41/image/namedpipe.gif')}}" border=0>
难点所在:
实现的过程比较简单,但有一个难点。原本当服务端使用ConnectNamedPipe函数后,如果有客户端连接,就可以直接进行交互。原来我在实现过程中,当管道空闲时,管道的线程函数会无限(INFINITE)阻塞。若现在需要停止服务,就必须结束所有的线程,TernimateThread可以作为一个结束线程的方法,但我基本不用这个函数。一旦使用这个函数之后,目标线程就会立即结束,但如果此时的目标线程正在操作互斥资源、内核调用、或者是操作共享DLL的全局变量,可能会出现互斥资源无法释放、内核异常等现象。这里我用重叠I/0来解决这个问题,在创建PIPE时使用FILE_FLAG_OVERLAPPED标志,这样使用ConnectNamedPipe后会立即返回,但线程的阻塞由等待函数WaitForSingleObject来实现,等待OVERLAPPED结构的事件对象被设置。
客户端主要代码:
代码:
void CMyDlg::OnSubmit() { // 打开管道 HANDLE hPipe = CreateFile("\\\\.\\Pipe\\NamedPipe", GENERIC_READ | GENERIC_WRITE, \ 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ; if ( hPipe == INVALID_HANDLE_VALUE ) { this->MessageBox ( "打开管道失败,服务器尚未启动,或者客户端数量过多" ) ; return ; } DWORD nReadByte, nWriteByte ; char szBuf[1024] = {0} ; // 把两个整数(a,b)格式化为字符串 sprintf ( szBuf, "%d %d", this->nFirst, this->nSecond ) ; // 把数据写入管道 WriteFile ( hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ; memset ( szBuf, 0, sizeof(szBuf) ) ; // 读取服务器的反馈信息 ReadFile ( hPipe, szBuf, 1024, &nReadByte, NULL ) ; // 把返回信息格式化为整数 sscanf ( szBuf, "%d", &(this->nResValue) ) ; this->UpdateData ( false ) ; CloseHandle ( hPipe ) ;}
服务端主要代码:
代码:
// 启动服务void CMyDlg::OnStart() { CString lpPipeName = "\\\\.\\Pipe\\NamedPipe" ; for ( UINT i = 0; i < nMaxConn; i++ ) { // 创建管道实例 PipeInst.hPipe = CreateNamedPipe ( lpPipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, \ PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, nMaxConn, 0, 0, 1000, NULL ) ; if ( PipeInst.hPipe == INVALID_HANDLE_VALUE ) { DWORD dwErrorCode = GetLastError () ; this->MessageBox ( "创建管道错误!" ) ; return ; } // 为每个管道实例创建一个事件对象,用于实现重叠IO PipeInst.hEvent = CreateEvent ( NULL, false, false, false ) ; // 为每个管道实例分配一个线程,用于响应客户端的请求 PipeInst.hTread = AfxBeginThread ( ServerThread, &PipeInst, THREAD_PRIORITY_NORMAL ) ; } this->SetWindowText ( "命名管道实例之服务器(运行)" ) ; this->MessageBox ( "服务启动成功" ) ;}// 停止服务void CMyDlg::OnStop() { DWORD dwNewMode = PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_NOWAIT ; for ( UINT i = 0; i < nMaxConn; i++ ) { SetEvent ( PipeInst.hEvent ) ; CloseHandle ( PipeInst.hTread ) ; CloseHandle ( PipeInst.hPipe ) ; } this->SetWindowText ( "命名管道实例之服务器" ) ; this->MessageBox ( "停止启动成功" ) ;}// 线程服务函数UINT ServerThread ( LPVOID lpParameter ){ DWORD nReadByte = 0, nWriteByte = 0, dwByte = 0 ; char szBuf[MAX_BUFFER_SIZE] = {0} ; PIPE_INSTRUCT CurPipeInst = *(PIPE_INSTRUCT*)lpParameter ; OVERLAPPED OverLapStruct = { 0, 0, 0, 0, CurPipeInst.hEvent } ; while ( true ) { memset ( szBuf, 0, sizeof(szBuf) ) ; // 命名管道的连接函数,等待客户端的连接(只针对NT) ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ; // 实现重叠I/0,等待OVERLAPPED结构的事件对象 WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ; // 检测I/0是否已经完成,如果未完成,意味着该事件对象是人工设置,即服务需要停止 if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) ) break ; // 从管道中读取客户端的请求信息 if ( !ReadFile ( CurPipeInst.hPipe, szBuf, MAX_BUFFER_SIZE, &nReadByte, NULL ) ) { MessageBox ( 0, "读取管道错误!", 0, 0 ) ; break ; } int a, b ; sscanf ( szBuf, "%d %d", &a, &b ) ; pMyDlg->nFirst = a ; pMyDlg->nSecond = b ; pMyDlg->nResValue = a + b ; memset ( szBuf, 0, sizeof(szBuf) ) ; sprintf ( szBuf, "%d", pMyDlg->nResValue ) ; // 把反馈信息写入管道 WriteFile ( CurPipeInst.hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ; pMyDlg->SetDlgItemInt ( IDC_FIRST, a, true ) ; pMyDlg->SetDlgItemInt ( IDC_SECOND, b, true ) ; pMyDlg->SetDlgItemInt ( IDC_RESULT, pMyDlg->nResValue, true ) ; // 断开客户端的连接,以便等待下一客户的到来 DisconnectNamedPipe ( CurPipeInst.hPipe ) ; } return 0 ;}