使用命名管道實作程序間通信
在Win32下提供的程序間通信方式有以下幾種:
Ø 剪貼闆Clipboard:在16位時代常使用的方式,CWnd類中提供了支援。
Ø COM/DCOM:通過COM系統的代理存根方式進行程序間資料交換,但隻能夠表現在對接口函數的調用時傳送資料,通過DCOM可以在不同主機間傳送資料。 Dynamic Data Exchange (DDE):在16位時代常使用的方式。
Ø File Mapping:檔案映射,在32位系統中提供的新方法,可用來共享記憶體。
Ø Mailslots:郵件槽,在32位系統中提供的新方法,可在不同主機間交換資料,分為伺服器方和客戶方,雙方可以通過其進行資料交換,在Win9X下隻支援郵件槽客戶。
Ø Pipes:管道,分為無名管道:在父子程序間交換資料;有名管道:可在不同主機間交換資料,分為伺服器方和客戶方,在Win9X下隻支援有名管道客戶。
Ø RPC:遠端過程調用,很少使用,原因有兩個:複雜而且與UNIX系統的RCP并不完全相容。但COM/DCOM的調用是建立在RPC的基礎上的。
Ø Windows Sockets:網絡套接口,可在不同主機間交換資料,分為伺服器方和客戶方。
Ø WM_COPYDATA:通過發送WM_COPYDATA消息并将資料放在參數中來傳遞資料給其他程序。
下面主要介紹一下命名管道的用法,命名管道是一個有名字,單向或雙向的通信管道。管道的名稱有兩部分組成:計算機名和管道名,例如//[host_name]/pipe/[pipe_name]/(括号内為參數)。對于同一主機來講允許有多個同一命名管道的執行個體并且可以由不同的程序打開,但是不同的管道都有屬于自己的管道緩沖區而且有自己的通訊環境互不影響,并且命名管道可以支援多個用戶端連接配接一個伺服器端。命名管道用戶端不但可以與本機上的伺服器通訊也可以同其他主機上的伺服器通訊。
命名管道的連接配接和通訊采用如下方式:
在伺服器端第一次建立命名管道後等待連接配接,當用戶端連接配接成功後伺服器端的命名管道就用作通訊用途。如果需要再次等待連接配接,伺服器端就需要再次打開命名管道(建立一個命名管道的執行個體)并等待連接配接。
對于用戶端每次打開命名管道後建立與伺服器間的連接配接,然後就可以利用命名管道進行通信,如果需要建立第二個連接配接則需要再次打開管道和再次建立連接配接。
建立命名管道時需要指定一個主機名和管道名,對于用戶端來說可以是如下格式://[host_name]/pipe/[pipe_name]/也可以是//./pipe/pipe_name/其中.表示本機。而伺服器端隻能夠在指定本機作為主機名,即隻能使用下面的格式://./pipe_name/。此外需要記住,在同一主機上管道名稱是唯一的,一個命名管道一旦被建立就不允許相同名稱的管道再被建立。
伺服器方通過:
HANDLE CreateNamedPipe(
LPCTSTR lpName, // pipe name
DWORD dwOpenMode, // pipe open mode
DWORD dwPipeMode, // pipe-specific modes
DWORD nMaxInstances, // maximum number of instances
DWORD nOutBufferSize, // output buffer size
DWORD nInBufferSize, // input buffer size
DWORD nDefaultTimeOut, // time-out interval
LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD
);
Ø 建立命名管道和打開已經存在的命名管道,其中lpName為管道名稱,dwOpenMode為建立方式,可以是下面值的組合: PIPE_ACCESS_INBOUND:管道隻能用作接收資料。 PIPE_ACCESS_OUTBOUND:管道隻能用作發送資料。 PIPE_ACCESS_DUPLEX:管道既可以發送也可以接收資料。(上面這三個值隻能夠取其中一個)
Ø FILE_FLAG_WRITE_THROUGH:管道用于同步發送和接收資料,隻有在資料被發送到目标位址時發送函數才會傳回,如果不設定這個參數那麼在系統内部對于命名管道的處理上可能會因為減少網絡附和而在資料積累到一定量時才發送,并且對于發送函數的調用會馬上傳回。
Ø FILE_FLAG_OVERLAPPED:管道可以用于異步輸入和輸出,異步讀寫的有關方法和檔案異步讀寫是相同的。
Ø dwPipeMode指定管道類型,可以是下面值的組合:
PIPE_TYPE_BYTE:資料在通過管道發送時作為位元組流發送,不能與PIPE_READMODE_MESSAGE共用。
PIPE_TYPE_MESSAGE:資料在通過管道發送時作為消息發送,不能與PIPE_READMODE_BYTE共用。
PIPE_READMODE_BYTE:在接收資料時接收位元組流。
PIPE_READMODE_MESSAGE:在接收資料時接收消息。
PIPE_WAIT:使用等待模式,在讀,寫和建立連接配接時都需要管道的另一方完成相應動作後才會傳回。
Ø PIPE_NOWAIT:使用非等待模式,在讀,寫和建立連接配接時不需要管道的另一方完成相應動作後就會立即傳回。
Ø nMaxInstances為管道的的最大數量,在第一次建立伺服器方管道時這個參數表明該管道可以同時存在的數量。PIPE_UNLIMITED_INSTANCES表明不對數量進行限制。nOutBufferSize和nInBufferSize表示緩沖區的大小。nDefaultTimeOut表示在等待連接配接時最長的等待時間(以毫秒為機關),如果在建立時設定為NMPWAIT_USE_DEFAULT_WAIT表明無限制的等待,而以後伺服器方的其他管道執行個體也需要設定相同的值。lpSecurityAttributes為安全屬性,一般設定為NULL。如果建立或打開失敗則傳回INVALID_HANDLE_VALUE。可以通過GetLastError得到錯誤。
客戶方通過:
HANDLE CreateFile(
LPCTSTR lpFileName, // file name
DWORD dwDesiredAccess, // access mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to template file
Ø 建立用戶端命名管道,CreateFile可以有很多用途,可以用來建立檔案,管道,郵件槽,目錄等,這裡介紹用CreateFile來打開用戶端命名管道。lpFileName用來指明管道名稱。dwDesiredAccess用來表明使用方式,可以使用下面的值: GENERIC_READ:打開一個隻用于讀的管道。GENERIC_WRITE:打開一個隻用于寫的管道。GENERIC_READ | GENERIC_WRITE:打開一個用于讀和寫的管道。 dwShareMode指定共享方式,一般指定為0,lpSecurityAttributes為安全屬性,一般設定為NULL,dwCreationDisposition設定為OPEN_EXISTING,dwFlagsAndAttributes設定為FILE_ATTRIBUTE_NORMAL,此外可以還設定為FILE_FLAG_OVERLAPPED來進行異步通訊,hTemplateFile設定為NULL。如果打開失敗則傳回INVALID_HANDLE_VALUE。可以通過GetLastError得到錯誤。
此外客戶方可以利用:
BOOL CallNamedPipe(
LPCTSTR lpNamedPipeName, // pipe name
LPVOID lpInBuffer, // write buffer
DWORD nInBufferSize, // size of write buffer
LPVOID lpOutBuffer, // read buffer
DWORD nOutBufferSize, // size of read buffer
LPDWORD lpBytesRead, // number of bytes read
DWORD nTimeOut // time-out value
來建立一個發送消息的管道。 管道的連接配接管理,客戶方在調用CreateFile後立即就能夠建立伺服器的連接配接,而伺服器方一旦管道打開或建立後可以用
BOOL ConnectNamedPipe(
HANDLE hNamedPipe, // handle to named pipe
LPOVERLAPPED lpOverlapped // overlapped structure
來等待用戶端的連接配接建立。如果希望在伺服器方檢測是否有連接配接到達,可以調用
BOOL WaitNamedPipe(
DWORD nTimeOut // time-out interval
這裡的lpNamePipeName直接使用建立管道時的名稱,如果在伺服器方希望關閉連接配接則調用
BOOL DisconnectNamedPipe(
HANDLE hNamedPipe // handle to named pipe
);
一旦連接配接被關閉,伺服器方可以再次調用ConnectNamedPipe來建立連接配接。如果要關閉管道則直接調用CloseHandle。請注意這裡提到的關閉管道和關閉連接配接是不同的意思,在同一個管道上可以依次反複建立連接配接,而且可以減小系統的負荷。而且如果指定了管道最大數量限制那麼在打開的管道達到最大限制後如果不關閉舊管道就無法打開新管道。
對于客戶方則無法關閉連接配接,而隻能直接調用CloseHandle關閉管道。
資料的發送,不論是伺服器還是客戶方都可以通過ReadFile和WriteFile進行管道讀寫來達到通訊的目的。
下面是一個例子,伺服器方建立或打開一個管道并讀入對方發送的資料,将小寫字母轉換成大寫字母後傳回,而客戶發建立一個到伺服器的連接配接并發送一個字元串并讀回經過轉換的資料:
在使用這個例子時,運作三個服務端程序,而運作第四個時會因為達到管道數量限制而打開管道失敗。
//服務方
void CNamed_pipeDlg::OnCreateP()
{
DWORD dwTO = NMPWAIT_USE_DEFAULT_WAIT;//設定連接配接等待時間
HANDLE hSvr=
CreateNamedPipe("////.//pipe//test_pipe//",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE,3,256,256,dwTO,NULL);
if( INVALID_HANDLE_VALUE == hSvr)
{
AfxMessageBox("Error create/open pipe");
}
else
if (ConnectNamedPipe(hSvr,NULL))
{
BYTE bRead;
DWORD dwRead,dwWritten;
while (ReadFile(hSvr,&bRead,1,&dwRead,NULL))
{
if(bRead >= 'a' && bRead $lt;='z')
bRead = 'A'+ (bRead-'a');
WriteFile(hSvr,&bRead,1,&dwWritten,NULL);
}
}
else
AfxMessageBox("error when waiting connected");
CloseHandle(hSvr);
}
//用戶端
void CNamed_pipe_cDlg::OnConn()
HANDLE hClient = CreateFile("////.//pipe//test_pipe//",GENERIC_WRITE |GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hClient == INVALID_HANDLE_VALUE)
AfxMessageBox("Error open pipe");
DWORD dwRead,dwWritten;
char szSend[10]="send...";
char szRecv[10];
for(int i=0;i<strlen(szSend)+1;i++)
WriteFile(hClient,szSend+i,1,&dwWritten,NULL);
ReadFile(hClient,szRecv+i,1,&dwRead,NULL);
CloseHandle(hClient);//close pipe
AfxMessageBox(szRecv);