命令管道是进程间通讯的一种常用方式,对于命令管道的介绍可以参考别的资料和书籍,这里推荐一个《VC++下命名管道编程的原理及实现》这篇博文,写得比较清楚。但是都是介绍了阻塞模式的编程,我这里主要是介绍利用命令管道OVERLAPPED方式使用非阻塞模式编程。注:文中使用的方法就是函数的意思。
参考MSDN,服务器端创建命令管道(使用CreateNamedPipe方法),不使用FILE_FLAG_OVERLAPPED模式时,当使用ConnectNamedPipe方法时,服务器端会进入阻塞。我们一般处理会创建一个工作线程,在工作线程中使用命令管道,但是会引入一个问题,当我们的程序退出时,这个工作线程没有办法结束,会阻塞在ConnectNamedPipe方法中。使用OVERLAPPED方式可以很好的解决这个问题。在codeproject上有一篇文章《One use for Overlapped I/O》写的比较好,提出了解决方法,大致的思路用下面的代码表示:
OVERLAPPED op; HANDLE h, handleArray[2]; BOOL bStop = FALSE; memset(&op, 0, sizeof(op)); handleArray[0] = op.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); handleArray[1] = gbl_hStopEvent; while (bStop == FALSE) { h = CreateNamedPipe(FILE_FLAG_OVERLAPPED); ConnectNamedPipe(h, &op); switch (WaitForMultipleObjects(2, handleArray, FALSE, INFINITE)) { case WAIT_OBJECT_0: _beginthread(threadProc, 0, h); ResetEvent(handleArray[0]); break; case WAIT_OBJECT_0 + 1: CloseHandle(h); bStop = TRUE; break; } } CloseHandle(handleArray[0]);
大致的思路是创建2个事件,一个用于退出线程,一个用于OVERLAPPED接受的event事件绑定,在CreateNamedPipe时,
使用FILE_FLAG_OVERLAPPED模式,这样在ConnetNamePipe时,就不会阻塞,会立即返回。然后使用
WaitForMultipleObjects方法等待这两个事件。这样就可以实现退出。
这篇文章提出的思路是对的,但是在解决实现上还需要细化。
首先,对于创建的2个事件,参考MSDN,与OVERLAPPED结构关联的event,要是manual-reset event,同时初始态要为signaled,
也就是要使用参数TRUE(为什么初始态要为signaled,这个后面解释)。另外一个是退出线程的事件。在使用
CreateNamedPipe(FILE_FLAG_OVERLAPPED)(伪码);
使用ConnectNamedPipe(h, &op)后,会立即返回,这个时候一般是返回FALSE,使用GetLastError()会得到
ERROR_IO_PENDING,表示这个请求是悬而未决的。我使用一个BOOL fPendingIO标识来记录所有悬而未决的的请求,
fPendingIO=TRUE。然后使用WaitForMultipleObjects方法等待这2个事件。线程现在就会阻塞在这里,
直到有相关的事件处于signaled态。现在来解释一下为什么开始创建事件时初始态为signaled。按照常理,
WaitForMultipleObjects不会被阻塞,因为其中一个事件的状态为signaled。其实不然,它的状态在
connectNamedPipe(h, &op)后已经改变了。对以OVERLAPPED关联的事件,
当使用OVERLAPPED相关的方法操作后,其状态会可能会改变的,主要基于下面3个原则:
1)当实际操作在函数返回前已经完成,事件的状态不会改变。
2)当函数返回是,实际的操作没有完成,也即是说这个操作是Pending的,这个时候事件会被设置为nonsignaled.
3) 当操作的Pending完成后,事件会被设置为signaled。
有了上面的3条原则,OVERLAPPED关联的事件的状态变化就好理解了。当使用connectNamedPipe(h, &op)方法时,
函数会立即返回,而实际这个操作并没有进行,而是Pending了,所以,event会由signaled变为nonsignaled,
当真正有Client连接时,这个操作才会完成,这个时候,event会由nonsignaled变为signaled。这个时候,
WaitForMultipleObjects会继续执行下去。对于Pending后的操作,一定要使用
GetOverlappedResult方法,判断结果。
上面的原则适用ReadFile, WriteFile, ConnectNamedPipe, 和 TransactNamedPipe等函数。
下面是我的代码,设计思路是利用namedpipe实现2个进程间的通讯,客户端发送3个整数给服务器端。
Server端:
m_hEvents[0] = CreateEvent(NULL,TRUE,TRUE,NULL); // OVERLPPED‘s event m_hEvents[1] = CreateEvent(NULL,TRUE,FALSE,NULL); // exit event
namepipe线程:
NamedPipeWorkThread(LPVOID lParam) { TRACE("NamedPipeWorkThread/n"); CServerDlg * pDlg = (CServerDlg*)lParam; BOOL fSuccess; OVERLAPPED op; memset(&op,0,sizeof(op)); op.hEvent = pDlg->m_hEvents[0]; LPTSTR lpszPipename = TEXT("////.//pipe//mynamedpipe"); HANDLE hPipeInst = CreateNamedPipe( lpszPipename, // pipe name PIPE_ACCESS_DUPLEX | // read/write access FILE_FLAG_OVERLAPPED, // overlapped mode PIPE_TYPE_MESSAGE | // message-type pipe PIPE_READMODE_MESSAGE | // message-read mode PIPE_WAIT, // blocking mode 1, // number of instances BUFSIZE*sizeof(TCHAR), // output buffer size BUFSIZE*sizeof(TCHAR), // input buffer size PIPE_TIMEOUT, // client time-out NULL); // default security attributes if(hPipeInst == INVALID_HANDLE_VALUE) { AfxMessageBox("CreateNamedPipe failed with %d./n", GetLastError()); return 0; } PT_COLOR PtColor; DWORD dwBytesReaded; BOOL fConnected,fPendingIO = FALSE; fConnected = ConnectNamedPipe(hPipeInst, &op); if (fConnected) { AfxMessageBox("ConnectNamedPipe failed with %d./n", GetLastError()); return 0; } switch (GetLastError()) { // The overlapped connection in progress. case ERROR_IO_PENDING: fPendingIO = TRUE; break; // Client is already connected, so signal an event. case ERROR_PIPE_CONNECTED: if (SetEvent(op.hEvent)) break; // If an error occurs during the connect operation... default: { AfxMessageBox("ConnectNamedPipe failed with %d./n", GetLastError()); return 0; } } DWORD dwRet; BYTE btState = 0; while(1){ DWORD dwResult = WaitForMultipleObjects(2,pDlg->m_hEvents,FALSE,INFINITE); if(0 == dwResult - WAIT_OBJECT_0){ if(fPendingIO){ fSuccess = GetOverlappedResult( hPipeInst, // handle to pipe &op, // OVERLAPPED structure &dwRet, // bytes transferred FALSE); // do not wait switch(btState){ case CONNECTING_STATE: if (! fSuccess) { AfxMessageBox("Error %d./n", GetLastError()); return 0; } btState = READING_STATE; break; case READING_STATE: if(!fSuccess || dwRet == 0){ DisconnectNamedPipe(hPipeInst); return 0; } TRACE("Read bytes = %d/n",dwRet); TRACE("nX=%d,nY=%d,nColor=%d/n",PtColor.nX,PtColor.nY,PtColor.nColor); break; } } fSuccess = ReadFile( hPipeInst,&PtColor, sizeof(PT_COLOR),&dwBytesReaded,&op); if(fSuccess && dwBytesReaded != 0){ fPendingIO = FALSE; TRACE("Read bytes = %d/n",dwBytesReaded); TRACE("nX=%d,nY=%d,nColor=%d/n",PtColor.nX,PtColor.nY,PtColor.nColor); continue; } DWORD dwErr = GetLastError(); if (! fSuccess && (dwErr == ERROR_IO_PENDING)) { fPendingIO = TRUE; continue; } } else{ break; } } DisconnectNamedPipe(hPipeInst); TRACE("exit NamedPipeWorkThread/n"); return 0; }
客户端:
typedef struct Tag_Pt_Color{ int nX; int nY; int nColor; }PT_COLOR;
LPTSTR lpszPipename = TEXT("////.//pipe//mynamedpipe"); m_hPipe = CreateFile( lpszPipename, // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing NULL, // default security attributes OPEN_EXISTING, // opens existing pipe 0, // default attributes NULL); // no template file if(INVALID_HANDLE_VALUE == m_hPipe){ AfxMessageBox("Create NamedPipe failed with %d./n"); return; } DWORD dwMode = PIPE_READMODE_MESSAGE; BOOL fSuccess = SetNamedPipeHandleState( m_hPipe, // pipe handle &dwMode, // new pipe mode NULL, // don't set maximum bytes NULL); // don't set maximum time if ( ! fSuccess) { CString strMsg; strMsg.Format("SetNamedPipeHandleState failed. GLE=%d/n", GetLastError()); AfxMessageBox(strMsg); return ; }
PT_COLOR ptColor;
ptColor.nX = m_nX;
ptColor.nY = m_nY;
ptColor.nColor = m_nColor;
DWORD dwBytesWritten;
BOOL fSuccess = WriteFile(m_hPipe,&ptColor,sizeof(PT_COLOR),&dwBytesWritten,NULL);
if (!fSuccess)
{
CString strMsg;
strMsg.Format("WriteFile to pipe failed. GLE=%d/n", GetLastError());
AfxMessageBox(strMsg);
return ;
}