1. 程式人生 > >VC++下命名管道程式設計的原理及實現

VC++下命名管道程式設計的原理及實現

  管道(Pipe)實際是用於程序間通訊的一段共享記憶體,建立管道的程序稱為管道伺服器,連線到一個管道的程序為管道客戶機。命名管道(Named Pipes)是在管道伺服器和一臺或多臺管道客戶機之間進行單向或雙向通訊的一種命名的管道。一個命名管道的所有例項共享同一個管道名,但是每一個例項均擁有獨立的快取與控制代碼,並且為客戶——服務通訊提供有一個分離的管道。例項的使用保證了多個管道客戶能夠在同一時間使用同一個命名管道。

  Microsoft Windows NT、Windows 2000、Windows 95以及Windows 98均提供對命名管道的支援(不包括Windows CE),但只有Windows NT和Windows 2000才支援伺服器端的命名管道技術。命名管道可以在同一臺計算機的不同程序之間,或在跨越一個網路的不同計算機的不同程序之間進行有連線的可靠資料通訊,如果連線中斷,連線雙方都能立即收到連線斷開的資訊。命令管道是圍繞Windows檔案系統而設計的一種機制,採用的是命名管道檔案系統(Named Pipe File System, NPFS)介面。對資料的收發也採用檔案讀寫函式ReadFile()和WriteFile()來完成。在設計上,由於命名管道也利用了微軟網路提供者(MSNP)重定向器,因此無需涉及底層的通訊協議細節。命名管道還充分利用了Windows NT及Windows 2000內建的安全特性,通訊的安全性相對較好。

  命名規範及通訊模式


  每一個命名管道都有一個唯一的名字以區分於存在於系統的命名物件列表中的其他命名管道。管道伺服器在呼叫CreateNamedPipe()函式建立命名管道的一個或多個例項時為其指定了名稱。對於管道客戶機,則是在呼叫CreateFile()或CallNamedPipe()函式以連線一個命名管道例項時對管道名進行指定。命名管道的命名規範與郵槽有些類似,對其標識也是採用的UNC格式:

//Server/Pipe/[Path]Name


  其中,第一部分//Server指定了伺服器的名字,命名管道服務即在此伺服器建立,其字串部分可表示為一個小數點(表示本機)、星號(當前網路欄位)、域名或是一個真正的服務;第二部分/Pipe與郵槽的/Mailslot一樣是一個不可變化的硬編碼字串,以指出該檔案是從屬於NPFS;第三部分/[Path]Name則使應用程式可以唯一定義及標識一個命名管道的名字,而且可以設定多級目錄。

  命名管道提供了兩種基本的通訊模式:位元組模式和訊息模式。可在CreateNamePipe()建立命名管道時分別用PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE標誌進行設定。在位元組模式中,資訊以連續位元組流的形式在客戶與伺服器之間流動。這也就意味著,對於客戶機應用和伺服器應用,在任何一個特定的時間段內,都無法準確知道有多少位元組從管道中讀出或寫入。在這種通訊模式中,一方在向管道寫入某個數量的位元組後,並不能保證管道另一方能讀出等量的位元組。對於訊息模式,客戶機和伺服器則是通過一系列不連續的資料包進行資料的收發。從管道發出的每一條訊息都必須作為一條完整的訊息讀入。

  使用命名管道

  管道伺服器首次呼叫CreateNamedPipe()函式時,使用nMaxInstance引數指定了能同時存在的管道例項的最大數目。伺服器可以重複呼叫CreateNamedPipe()函式去建立管道新的例項,直至達到設定的最大例項數。下面給出CreateNamedPipe()的函式原型:

HANDLE CreateNamedPipe(
 LPCTSTR lpName, // 指向管道名稱的指標
 DWORD dwOpenMode, // 管道開啟模式
 DWORD dwPipeMode, // 管道模式
 DWORD nMaxInstances, // 最大例項數
 DWORD nOutBufferSize, // 輸出快取大小
 DWORD nInBufferSize, // 輸入快取大小
 DWORD nDefaultTimeOut, // 超時設定
 LPSECURITY_ATTRIBUTES lpSecurityAttributes // 安全屬性指標
);

  如果在已定義超時值變為零以前,有一個例項管道可以使用,則建立成功並返回管道控制代碼,以此偵聽來自客戶機的連線請求。另一方面,客戶機通過函式WaitNamedPipe()使伺服器程序等待來自客戶的例項連線。如果在超時值變為零以前,有一個管道可供連線使用,則函式將成功返回,並通過呼叫CreateFile()或CallNamedPipe()來呼叫對伺服器的連線。此時伺服器將接受客戶的連線請求,成功建立連線,伺服器呼叫的等待客戶機建立連線的ConnectNamedPipe()函式也將成功返回。

  從呼叫時序上看,首先是客戶機通過WaitNamedPipe()使伺服器的CreateFile()在限時時間內建立例項成功,然後雙方通過ConnectNamedPipe()和CreateFile()成功連線,在返回用以通訊的檔案控制代碼後,客戶、服務雙方即可進行通訊。

  在建立了連線後,客戶機與伺服器即可通過ReadFile()和WriteFile()並利用得到的管道控制代碼,以檔案讀寫的形式彼此間進行資訊交換。 當客戶與伺服器的通訊結束,或是由於某種原因一方需要斷開時,由客戶機呼叫CloseFile()函式關閉開啟的管道控制代碼,伺服器隨即呼叫DisconnectNamedPipe()函式。當然,伺服器也可以通過單方面呼叫DisconnectNamedPipe()來終止連線。在終止連線後呼叫函式CloseHandle()來關閉此管道。下面給出的程式清單即是按照上述方法實現的命名管道伺服器和客戶機進行通訊的簡單程式實現程式碼:
伺服器端:

m_hPipe = CreateNamedPipe("////.//Pipe//Test", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 1000, NULL); // 建立命名管道
if (m_hPipe == INVALID_HANDLE_VALUE)
 m_sMessage = "建立命名管道失敗!";
else{
 m_sMessage = "成功建立命名管道!";
 AfxBeginThread(ReadProc, this); // 開啟執行緒
}

  由於ConnectNamedPipe()函式在沒有客戶機連線到伺服器時會無限等待下去,因此為避免由此引起主執行緒的阻塞,為其開闢了一個子執行緒ReadProc:

UINT ReadProc(LPVOID lpVoid)
{
 char buffer[1024]; // 資料快取
 DWORD ReadNum;
 CServerView* pView = (CServerView*)lpVoid; // 獲取視控制代碼
 if (ConnectNamedPipe(pView->m_hPipe, NULL) == FALSE) // 等待客戶機的連線
 {
  CloseHandle(pView->m_hPipe); // 關閉管道控制代碼
  pView->m_sMessage = "與客戶機建立連線失敗!"; // 顯示資訊
  pView->Invalidate();
  return 0;
 }else{
  pView->m_sMessage = "與客戶機建立連線!"; // 顯示資訊
  pView->Invalidate();
 }
 // 從管道讀取資料
 if (ReadFile(pView->m_hPipe, buffer, sizeof(buffer), &ReadNum, NULL) == FALSE)
 {
  CloseHandle(pView->m_hPipe); // 關閉管道控制代碼
  pView->m_sMessage = "從管道讀取資料失敗!"; // 顯示資訊
  pView->Invalidate();
 } else {
  buffer[ReadNum] = '/0'; // 顯示接收到的資訊
  pView->m_sMessage = CString(buffer);
  pView->Invalidate();
 }
 return 1;
}

  在客戶同伺服器建立連線後,ConnectNamedPipe()才會返回,其下語句才得以執行。隨後的ReadFile()將負責把客戶寫入管道的資料讀取出來。在全部操作完成後,伺服器可以通過呼叫函式DisconnectNamedPipe()而終止連線:

if (DisconnectNamedPipe(m_hPipe) == FALSE) // 終止連線
 m_sMessage = "終止連線失敗!";
else
{
 CloseHandle(m_hPipe); // 關閉管道控制代碼
 m_sMessage = "成功終止連線!";
}

  客戶機端:

CString Message = "[測試資料,由客戶機發出]"; // 要傳送的資料
DWORD WriteNum; // 傳送的是資料長度
// 等待與伺服器的連線
if (WaitNamedPipe("////.//Pipe//Test", NMPWAIT_WAIT_FOREVER) == FALSE)
{
 m_sMessage = "等待連線失敗!"; // 顯示資訊
 Invalidate();
 return;
}
// 開啟已建立的管道控制代碼
HANDLE hPipe = 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();
}
// 向管道寫入資料
if (WriteFile(hPipe, Message, Message.GetLength(), &WriteNum, NULL) == FALSE)
{
 m_sMessage = "資料寫入管道失敗!"; // 顯示資訊
 Invalidate();
} else {
 m_sMessage = "資料成功寫入管道!"; // 顯示資訊
 Invalidate();
}
CloseHandle(hPipe); // 關閉管道控制代碼