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); // 關閉管道句柄 |
原文:VC++下命名管道編程的原理及實現?
總結
以上是生活随笔為你收集整理的VC++下命名管道编程的原理及实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UNICODE转多字节
- 下一篇: 通用SQL数据库查询语句精华使用简介