前面介紹了進程間通信的兩種方法:剪貼板和匿名管道。這兩種進程間通信的方法只能在本地主機的進程之間通信。而匿名管道還限制通信的兩進程之間必須有父子關系。在開發網絡間不同進程之間相互通信的應用程序時,我們可以用命名管道和郵槽。這兩種方法不僅支持本地主機通信也支持網間進程通信。下面詳細介紹這兩種方法:
一、命名管道
將命名管道作為網絡通信的方案時,他實際上建立了一個客戶機/服務器通信體系,並在其中可靠的傳輸數據。命名管道式圍繞Windows文件系統設計的一種機制,采用“命名管道文件系統借口”,因此客戶機和服務器可利用標准的Win32文件系統函數來進行數據的收發。在利用命名管道進行通信時,服務器是唯一一個有權建立命名管道的進程,可以接收客戶機的連接請求,客戶機只能同一個現有的命名管道客戶機建立連接。命名管道提供了兩種基本的通信模式:字節模式和消息模式。在實際編程過程中可以根據實際需要選擇不同的通信模式。利用命名管道進行通信可以按照下面的步驟進行:
(一)服務器端:
(1)創建命名管道
在利用命名管道進行通信之前,肯定要先創建命名管道。創建命名管道需調用的函數為CreateNamedPipe(),該函數的創建一個命名管道的實例並返回該實例的句柄。該函數的第一個參數用指定的格式保存創建的管道的名字。該函數的餓第二個參數指定管道的進入模式、重疊模式、讀寫模式、安全屬性等信息,在下面的示例中我用的是雙向的進入模式(PIPE_ACCESS_DUPLEX),客戶機和服務器都可以從管道讀寫數據,重疊方式為可重疊的方式(FILE_FLAG_OVERLAPPED),指定重疊模式後,需要花費時間的線程會立即返回執行其他操作,耗費時間的線程會在後台完成。該函數的第四個參數指定該管道可創建的最大實例個數。指定該參數的原因是,一個命名管道的示例只能和一個客戶端通信,如果有多個客戶端要通過該命名管道和服務器通信,需要創建多個該命名管道的實例。創建命名管道的示例代碼如下:
/***************************************************
創建命名管道
*******************************************************/
hPipe=CreateNamedPipe("\\\\.\\pipe\\MyPipe",PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
0,1,1024,1024,0,NULL);
if (INVALID_HANDLE_VALUE==hPipe)
{
MessageBox("創建命名管道失敗!");
hPipe=NULL;
//CloseHandle(hPipe);
return;
}
(2)等待客戶端連接請求的到來
命名管道創建完成之後,服務器就可以等待客戶端的連接請求。等待客戶端的連接請求調用的函數為ConnectNamedPipe()該函數的的第二個參數是一個指向OVERLAPPED結構體的指針,該結構體包含用於異步執行I/O操作的信息。該結構體中我們感興趣的參數是一個指向事件對象的句柄,我們可以利用該事件對象來執行異步I/O操作。所以我們要先調用CreateEvent()創建一個人工重置的事件對象。當等待客戶端連接請求之後,我們就可以調用WaitForSingleObject()函數將該事件對象置為無信號狀態,以供下一次連接請求使用。實現代碼如下:
/*********************************************************
創建人工重置的匿名事件對象
*********************************************************/
HANDLE hEvent;
hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if (!hEvent)
{
MessageBox(" 創建人工重置的匿名事件對象失敗!");
CloseHandle(hEvent);
hPipe=NULL;
return;
}
/*********************************************************
等待客戶端連接請求的到來
*********************************************************/
OVERLAPPED olp;
ZeroMemory(&olp,sizeof(OVERLAPPED));
olp.hEvent=hEvent;
if(!ConnectNamedPipe(hPipe,&olp))
{
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);
(3)往管道中執行讀寫操作
由於命名管道式圍繞文件系統設計的,所以我們可以利用標准的Win32文件操作函數執行讀寫操作。示例代碼如下:
void CNamedPipeSrvView::OnPipRead()
{
// TODO: Add your command handler code here
char buf[200];
DWORD dwRead;
if(!ReadFile(hPipe,buf,200,&dwRead,NULL))
{
MessageBox("讀取匿名管道失敗!");
return;
}
MessageBox(buf);
}
void CNamedPipeSrvView::OnPipWrite()
{
// TODO: Add your command handler code here
char buf[]="匿名管道測試程序!";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("寫匿名管道失敗!");
return;
}
}
(二)客戶端
命名管道的實現比較簡單了,客戶端首先判斷是否有可利用的命名管道實例,如果有,然後打開管道就可以進行讀寫操作了。
(1)連接管道實例並打開管道
WaitNamedPipe()函數可用來連接一個命名管實例,連接成功後,就可以調用CreateFile()函數打開管道,該函數可以指定管道進入的模式、文件屬性等。示例代碼如下:
**********************************************************
判斷是否有可利用的命名管道實例
************************************************************/
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;
}
(2)往管道進行讀寫操作
void CNamedPipeCltView::OnPipRead()
{
// TODO: Add your command handler code here
char buf[200];
DWORD dwRead;
if(!ReadFile(hPipe,buf,200,&dwRead,NULL))
{
MessageBox("讀取匿名管道失敗!");
return;
}
MessageBox(buf);
}
void CNamedPipeCltView::OnPipWrite()
{
// TODO: Add your command handler code here
char buf[]="乘風736博客園 http://www.cnblogs.com/chengfeng736";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("寫匿名管道失敗!");
return;
}
}
運行結果如下:
這樣,一個通過命名管道進行通信的服務器和客戶端程序就設計好了。兩個進程就可以實現通信了。
二 、郵槽
郵槽是基於廣播模式的單向通信方式,服務器只能從郵槽讀取數據,客戶端只能往郵槽寫入數據,而且利用郵槽通信的信息量不能太大。下面介紹利用郵槽進行進程通信的過程:
(一)服務器
(1)創建油槽
創建油槽可調用CreateMailslot()函數實現,該函數的第一個參數按照指定格式指定油槽的名字,第三個參數指定讀操作等待的時間。下面的示例程序我設定讀操作一直等待(MAILSLOT_WAIT_FOREVER),只到接收到數據為止。示例代碼如下:
HANDLE hMailslot;
hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailslot",0,MAILSLOT_WAIT_FOREVER,NULL);
if (INVALID_HANDLE_VALUE==hMailslot)
{
MessageBox("創建郵槽失敗!");
CloseHandle(hMailslot);
return;
}
(2)讀取數據
DWORD dwRead;
char buf[200];
if(!ReadFile(hMailslot,buf,200,&dwRead,NULL))
{
MessageBox("讀取數據失敗!");
CloseHandle(hMailslot);
return;
}
MessageBox(buf);
CloseHandle(hMailslot);
(二)客戶端
(1)打開油槽
CreateFile()函數不僅可以打開文件、管道還可以打開油槽。示例代碼如下:
HANDLE hMailslot;
hMailslot=CreateFile("\\\\.\\mailslot\\MyMailslot",GENERIC_WRITE,FILE_SHARE_READ,
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (INVALID_HANDLE_VALUE==hMailslot)
{
MessageBox("打開郵槽失敗!");
CloseHandle(hMailslot);
return;
}
(2)寫入數據
char buf[]="使用郵槽進行進程間通信\r\n乘風736博客園 http://www.cnblogs.com/chengfeng736";
DWORD dwWrite;
if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,0))
{
MessageBox("寫入數據失敗!");
CloseHandle(hMailslot);
return;
}
CloseHandle(hMailslot);
運行結果如下:
使用油槽通信的實現是很簡單的。油槽只能單向通信,如果要實現雙向通信,可以在客戶端和服務器分別實現讀寫操作就可以了