概述
管道(Pipe)實(shí)際是用于進(jìn)程間通信的一段共享內(nèi)存,創(chuàng)建管道的進(jìn)程稱為管道服務(wù)器,連接到一個管道的進(jìn)程為管道客戶機(jī)。命名管道(NamedPipes)是在管道服務(wù)器和一臺或多臺管道客戶機(jī)之間進(jìn)行單向或雙向通信的一種命名的管道。一個命名管道的所有實(shí)例共享同一個管道名,但是每一個實(shí)例均擁有獨(dú)立的緩存與句柄,并且為客戶——服務(wù)通信提供有一個分離的管道。實(shí)例的使用保證了多個管道客戶能夠在同一時間使用同一個命名管道。
Microsoft Windows NT、Windows 2000、Windows 95以及Windows98均提供對命名管道的支持(不包括Windows CE),但只有Windows NT和Windows2000才支持服務(wù)器端的命名管道技術(shù)。命名管道可以在同一臺計算機(jī)的不同進(jìn)程之間,或在跨越一個網(wǎng)絡(luò)的不同計算機(jī)的不同進(jìn)程之間進(jìn)行有連接的可靠數(shù)據(jù)通信,如果連接中斷,連接雙方都能立即收到連接斷開的信息。命令管道是圍繞Windows文件系統(tǒng)而設(shè)計的一種機(jī)制,采用的是命名管道文件系統(tǒng)(NamedPipe File System,NPFS)接口。對數(shù)據(jù)的收發(fā)也采用文件讀寫函數(shù)ReadFile()和WriteFile()來完成。在設(shè)計上,由于命名管道也利用了微軟網(wǎng)絡(luò)提供者(MSNP)重定向器,因此無需涉及底層的通信協(xié)議細(xì)節(jié)。命名管道還充分利用了Windows NT及Windows2000內(nèi)建的安全特性,通信的安全性相對較好。
命名規(guī)范及通信模式
每一個命名管道都有一個唯一的名字以區(qū)分于存在于系統(tǒng)的命名對象列表中的其他命名管道。管道服務(wù)器在調(diào)用CreateNamedPipe()函數(shù)創(chuàng)建命名管道的一個或多個實(shí)例時為其指定了名稱。對于管道客戶機(jī),則是在調(diào)用CreateFile()或CallNamedPipe()函數(shù)以連接一個命名管道實(shí)例時對管道名進(jìn)行指定。命名管道的命名規(guī)范與郵槽有些類似,對其標(biāo)識也是采用的UNC格式:
\\Server\Pipe\[Path]Name
其中,第一部分\\Server指定了服務(wù)器的名字,命名管道服務(wù)即在此服務(wù)器創(chuàng)建,其字串部分可表示為一個小數(shù)點(diǎn)(表示本機(jī))、星號(當(dāng)前網(wǎng)絡(luò)字段)、域名或是一個真正的服務(wù);第二部分\Pipe與郵槽的\Mailslot一樣是一個不可變化的硬編碼字串,以指出該文件是從屬于NPFS;第三部分\[Path]Name則使應(yīng)用程序可以唯一定義及標(biāo)識一個命名管道的名字,而且可以設(shè)置多級目錄。
命名管道提供了兩種基本的通信模式:字節(jié)模式和消息模式。可在CreateNamePipe()創(chuàng)建命名管道時分別用PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE標(biāo)志進(jìn)行設(shè)定。在字節(jié)模式中,信息以連續(xù)字節(jié)流的形式在客戶與服務(wù)器之間流動。這也就意味著,對于客戶機(jī)應(yīng)用和服務(wù)器應(yīng)用,在任何一個特定的時間段內(nèi),都無法準(zhǔn)確知道有多少字節(jié)從管道中讀出或?qū)懭搿T谶@種通信模式中,一方在向管道寫入某個數(shù)量的字節(jié)后,并不能保證管道另一方能讀出等量的字節(jié)。對于消息模式,客戶機(jī)和服務(wù)器則是通過一系列不連續(xù)的數(shù)據(jù)包進(jìn)行數(shù)據(jù)的收發(fā)。從管道發(fā)出的每一條消息都必須作為一條完整的消息讀入。
使用命名管道
管道服務(wù)器首次調(diào)用CreateNamedPipe()函數(shù)時,使用nMaxInstance參數(shù)指定了能同時存在的管道實(shí)例的最大數(shù)目。服務(wù)器可以重復(fù)調(diào)用CreateNamedPipe()函數(shù)去創(chuàng)建管道新的實(shí)例,直至達(dá)到設(shè)定的最大實(shí)例數(shù)。下面給出CreateNamedPipe()的函數(shù)原型:
HANDLE CreateNamedPipe(
LPCTSTR lpName, // 指向管道名稱的指針
DWORD dwOpenMode, // 管道打開模式
DWORD dwPipeMode, // 管道模式
DWORD nMaxInstances, // 最大實(shí)例數(shù)
DWORD nOutBufferSize, // 輸出緩存大小
DWORD nInBufferSize, // 輸入緩存大小
DWORD nDefaultTimeOut, // 超時設(shè)置
LPSECURITY_ATTRIBUTES lpSecurityAttributes // 安全屬性指針
);
如果在已定義超時值變?yōu)榱阋郧埃幸粋€實(shí)例管道可以使用,則創(chuàng)建成功并返回管道句柄,以此偵聽來自客戶機(jī)的連接請求。另一方面,客戶機(jī)通過函數(shù)WaitNamedPipe()使服務(wù)器進(jìn)程等待來自客戶的實(shí)例連接。如果在超時值變?yōu)榱阋郧?,有一個管道可供連接使用,則函數(shù)將成功返回,并通過調(diào)用CreateFile()或CallNamedPipe()來呼叫對服務(wù)器的連接。此時服務(wù)器將接受客戶的連接請求,成功建立連接,服務(wù)器調(diào)用的等待客戶機(jī)建立連接的ConnectNamedPipe()函數(shù)也將成功返回。
從調(diào)用時序上看,首先是客戶機(jī)通過WaitNamedPipe()使服務(wù)器的CreateFile()在限時時間內(nèi)創(chuàng)建實(shí)例成功,然后雙方通過ConnectNamedPipe()和CreateFile()成功連接,在返回用以通信的文件句柄后,客戶、服務(wù)雙方即可進(jìn)行通信。
在建立了連接后,客戶機(jī)與服務(wù)器即可通過ReadFile()和WriteFile()并利用得到的管道句柄,以文件讀寫的形式彼此間進(jìn)行信息交換。當(dāng)客戶與服務(wù)器的通信結(jié)束,或是由于某種原因一方需要斷開時,由客戶機(jī)調(diào)用CloseFile()函數(shù)關(guān)閉打開的管道句柄,服務(wù)器隨即調(diào)用DisconnectNamedPipe()函數(shù)。當(dāng)然,服務(wù)器也可以通過單方面調(diào)用DisconnectNamedPipe()來終止連接。在終止連接后調(diào)用函數(shù)CloseHandle()來關(guān)閉此管道。下面給出的程序清單即是按照上述方法實(shí)現(xiàn)的命名管道服務(wù)器和客戶機(jī)進(jìn)行通信的簡單程序?qū)崿F(xiàn)代碼:
服務(wù)器端:
m_hPipe= CreateNamedPipe("\\\\.\\Pipe\\Test", PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 1000, NULL); // 創(chuàng)建命名管道
if (m_hPipe == INVALID_HANDLE_value)
m_sMessage = "創(chuàng)建命名管道失敗!";
else{
m_sMessage = "成功創(chuàng)建命名管道!";
AfxBeginThread(ReadProc, this); // 開啟線程
}
由于ConnectNamedPipe()函數(shù)在沒有客戶機(jī)連接到服務(wù)器時會無限等待下去,因此為避免由此引起主線程的阻塞,為其開辟了一個子線程ReadProc:
UINT ReadProc(LPVOID lpVoid)
{
char buffer[1024]; // 數(shù)據(jù)緩存
DWORD ReadNum;
CServerView* pView = (CServerView*)lpVoid; // 獲取視句柄
if (ConnectNamedPipe(pView->m_hPipe, NULL) == FALSE) // 等待客戶機(jī)的連接
{
CloseHandle(pView->m_hPipe); // 關(guān)閉管道句柄
pView->m_sMessage = "與客戶機(jī)建立連接失敗!"; // 顯示信息
pView->Invalidate();
return 0;
}else{
pView->m_sMessage = "與客戶機(jī)建立連接!"; // 顯示信息
pView->Invalidate();
}
// 從管道讀取數(shù)據(jù)
if (ReadFile(pView->m_hPipe, buffer, sizeof(buffer), &ReadNum, NULL) == FALSE)
{
CloseHandle(pView->m_hPipe); // 關(guān)閉管道句柄
pView->m_sMessage = "從管道讀取數(shù)據(jù)失敗!"; // 顯示信息
pView->Invalidate();
} else {
buffer[ReadNum] = '\0'; // 顯示接收到的信息
pView->m_sMessage = CString(buffer);
pView->Invalidate();
}
return 1;
}
在客戶同服務(wù)器建立連接后,ConnectNamedPipe()才會返回,其下語句才得以執(zhí)行。隨后的ReadFile()將負(fù)責(zé)把客戶寫入管道的數(shù)據(jù)讀取出來。在全部操作完成后,服務(wù)器可以通過調(diào)用函數(shù)DisconnectNamedPipe()而終止連接:
if (DisconnectNamedPipe(m_hPipe) == FALSE) // 終止連接
m_sMessage = "終止連接失敗!";
else
{
CloseHandle(m_hPipe); // 關(guān)閉管道句柄
m_sMessage = "成功終止連接!";
}
客戶機(jī)端:
CString Message = "[測試數(shù)據(jù),由客戶機(jī)發(fā)出]"; // 要發(fā)送的數(shù)據(jù)
DWORD WriteNum; // 發(fā)送的是數(shù)據(jù)長度
// 等待與服務(wù)器的連接
if (WaitNamedPipe("\\\\.\\Pipe\\Test", NMPWAIT_WAIT_FOREVER) == FALSE)
{
m_sMessage = "等待連接失敗!"; // 顯示信息
Invalidate();
return;
}
// 打開已創(chuàng)建的管道句柄
HANDLEhPipe = 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();
}
// 向管道寫入數(shù)據(jù)
if (WriteFile(hPipe, Message, Message.GetLength(), &WriteNum, NULL) == FALSE)
{
m_sMessage = "數(shù)據(jù)寫入管道失敗!"; // 顯示信息
Invalidate();
} else {
m_sMessage = "數(shù)據(jù)成功寫入管道!"; // 顯示信息
Invalidate();
}
CloseHandle(hPipe); // 關(guān)閉管道句柄