我們知道管道包括三種:
1):普通管道PIPE,通常有很多限制,一是半雙工,只能單向傳輸,二是只能在父子進程間使用
2):流管道:這種能雙向傳輸,但是也是只能父子進程間使用。
3):命名管道,去除了以上的第二種限制,可以在許多不相關的進程間進行通訊。也是半雙工的通信方式。
但是通常我們把管道分為匿名管道和命名管道。但對於匿名管道的話,只能在本機上進程之間通信,而且只能實現本地的父子進程之間的通信,局限性太大了。而這里介紹的命名管道,就和匿名管道有些不同了,在功能上也就顯得強大許多,至少其可以實現跨網絡之間的進程的通信,同時其客戶端既可以接收數據也可以發送數據,服務器端也是可以接收數據,又可以發送數據。
匿名管道的概述
對於匿名管道而言,命名管道使用了windows安全機制,因而命名管道的服務器端可以控制哪些客戶有權與其建立連接。哪些客戶端是不能夠與這個命名管道建立連接的。命名管道的通信是以連接的方式進行的,服務器創建一個命名管道對象,然后在此對象上等待連接請求,一旦客戶連接過來,則兩者都可以通過命名管道讀或者寫數據。
命名管道提供了兩種通信模式:字節模式和消息模式。在字節模式下,數據以一個連續的字節流的形式在客戶機和服務器之間流動。而在消息模式下,客戶機和服務器則通過一系列不連續的數據單位,進行數據的收發,每次在管道上發出一個消息后,它必須作為一個完整的消息讀入。
命名管道的使用步驟
服務器端:
1):服務器進程調用CreateNamedPipe函數來創建一個有名稱的命名管道在創建命名管道的時候必須指定一個本地的命名管道名稱。windows允許同一個本地的命名管道名稱右多個命名管道實例。所以,服務器進程在調用CreateNamedPipe函數時必須指定最大允許的實例數(0-255).如果CreateNamedPipe函數成功返回后,服務器進程得到一個指向一個命名管道實例的句柄。
2):服務器進程就可以調用ConnectNamedPipe來等待客戶的連接請求,這個ConnectNamedPipe既支持同步形式,又支持異步形式,若服務器進程以同步形式調用 ConnectNamedPipe函數,如果沒有得到客戶端的連接請求,則會一直等到客戶端的連接請求。當該函數返回時,客戶端和服務器之間的命名管道連接已經建立起來了。
3):這個時候服務器端就可以向客戶端讀(ReadFile)/寫(WriteFile)數據了。
4):在已經建立連接的命名管道實例中,服務器進程就會得到一個指向該管道實例的句柄,這個句柄稱之為服務器端句柄,同時服務端進程可以調用DisconnectNamedPipe函數,將一個管道實例與當前建立連接的客戶端進程斷開,從而可以重新連接到新的客戶端進程。當然,服務器也可以調用CloseHandle來關閉一個已經建立連接的命名管道實例。
客戶端:
1):客戶端進程調用CreateFile函數連接到一個正在等待連接的命名管道上。在這里客戶端需要指定將要連接的命名管道上。當CreateFile成功返回之后,客戶端就得到了一個指向已經建立連接的命名管道實例的句柄。在這里客戶端也可以先調用WaitNamedPipe函數來測試指定名稱的管道實例是否可用。在已經建立的命名管道實例中,客戶端進程就會得到一個指向該管道實例的句柄。這個句柄稱之為客戶端句柄。
2):這個時候客戶端就可以向服務器讀(ReadFile)/寫(WriteFile)數據了.
3):客戶端可以調用CloseHandle來關閉一個已經建立連接的命名管道實例。
函數介紹
1:
HANDLE WINAPI CreateNamedPipe( _In_ LPCTSTR lpName, _In_ DWORD dwOpenMode, _In_ DWORD dwPipeMode, _In_ DWORD nMaxInstances, _In_ DWORD nOutBufferSize, _In_ DWORD nInBufferSize, _In_ DWORD nDefaultTimeOut, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes );
該函數用來創建一個命名管道的實例,並返回這個命名管道的句柄。如果需要創建一個命名管道的多個實例,就需要多次調用CreateNamedPipe函數,參數 lpName 為一個字符串,其格式必須為 \\.\pipe\pipeName,其中圓點 ”.” 表示的是本地機器,如果想要與遠程的服務器建立連接,那么這個圓點位置處應指定這個遠程服務器的名稱,而其中的 “pipe” 這個是個固定的字符串,也就是說不能進行改變的,最后的 “pipename” 則代表的是我將要創建的命名管道的名稱了,參數 dwOpenMode 用來指定管道的訪問方式,重疊方式,寫直通方式,還有管道句柄的安全訪問方式。
命名管道的訪問方式如下表:
命名管道的寫直通方式和重疊方式
命名管道的安全訪問方式
命名管道句柄的類型
命名管道句柄的讀取方式
命名管道句柄的等待方式
nMaxInstance 指定命名管道能夠創建的實例的最大數目.該參數的取值可以從 0 – 255 ,這里說的最大實例數目是指對同一個命名管道最多能創建的實例數目。
nOutBufferSize 用來指定將要為輸出緩沖區所保留的字節數
nInBufferSize 用來指定將要為輸入緩沖區所保留的字節數
nDefaultTimeOut 用來指定默認的超時值,以毫秒為單位,同一個管道的不同實例必須指定同樣的超時值
lpSecurityAttributes 用來設置該命名管道的安全性,一般設置為 NULL ,也就是采用 Windows 提供的默認安全性
2:
BOOL WINAPI ConnectNamedPipe( _In_ HANDLE hNamedPipe, _Inout_opt_ LPOVERLAPPED lpOverlapped );
該函數的作用是讓服務器等待客戶端的連接請求的到來
hNamedPipe 指向一個命名管道實例的服務器的句柄
lpOverlapped 指向一個 OVERLAPPED結構的指針,
如果 hNamedPipe 所標識的命名管道是用 FILE_FLAG_OVERLAPPED,
(也就是重疊模式或者說異步方式)標記打開的,則這個參數不能為 NULL ,
必須是一個有效的指向一個 OVERLAPPED 結構的指針,否則該函數可能會錯誤的執行
3:
BOOL WINAPI WaitNamedPipe( _In_ LPCTSTR lpNamedPipeName, _In_ DWORD nTimeOut );
通過該函數可以判斷是否有可用的命名管道。直到等待的時間間隔已過,或者指定的命名管道的實例可以用來連接了。
lpNamedPipeName 用來指定命名管道的名稱,格式同CreateNamedPipe函數的lpNamedPipeName參數。
參數 nTimeOut 用來指定超時間隔,參數可以填寫系列的值::
NMPWAIT_USE_DEFAULT_WAIT::超時間隔即為服務器端創建該命名管道時指定的超時間隔。
NMPWAIT_USE_DEFAULT_WAIT::一直等待,直到出現一個可用的命名管道的實例。
命名管道的通信實例:
服務器端的實現
NamePipeServer.h
1 #ifndef NAME_PIPE_SERVER_H 2 #define NAME_PIPE_SERVER_H 3 4 #include<windows.h> 5 #include<iostream> 6 7 class NamePipeServer 8 { 9 public: 10 NamePipeServer() 11 { 12 pStr = "data from server"; 13 pPipeName = "\\\\.\\pipe\\testPipe"; 14 } 15 //創建命名管道 16 void CreateNamedPipeInServer(); 17 //從命名管道中讀取數據 18 void NamedPipeReadInServer(); 19 //往命名管道中寫入數據 20 void NamedPipeWriteInServer(); 21 private: 22 HANDLE hNamedPipe; 23 const char *pStr; 24 const char *pPipeName; 25 }; 26 27 #endif
NamePipeServer.cpp
1 #include "stdafx.h" 2 #include "NamePipeServer.h" 3 4 using namespace std; 5 void NamePipeServer::CreateNamedPipeInServer() 6 { 7 HANDLE hEvent; 8 OVERLAPPED ovlpd; 9 10 BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH]; 11 SECURITY_ATTRIBUTES sa; 12 13 sa.nLength = sizeof(SECURITY_ATTRIBUTES); 14 sa.bInheritHandle = TRUE; 15 sa.lpSecurityDescriptor = &sd; 16 17 InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); 18 SetSecurityDescriptorDacl(&sd, TRUE, (PACL) 0, FALSE); 19 //創建命名管道 20 //這里創建的是雙向模式且使用重疊模式(異步操作)的命名管道 21 hNamedPipe = CreateNamedPipe( L"\\\\.\\pipe\\testspipe",PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,0, 1, 1024, 1024, 0, &sa); 22 if( INVALID_HANDLE_VALUE == hNamedPipe ) 23 { 24 cout << GetLastError() << endl; 25 hNamedPipe = NULL; 26 cout << "創建命名管道失敗!!!" << endl << endl; 27 return; 28 } 29 hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 30 if( !hEvent ) 31 { 32 cout<<"創建事件失敗 ..."<< endl<< endl; 33 return; 34 } 35 memset(&ovlpd, 0, sizeof(OVERLAPPED)); 36 ovlpd.hEvent = hEvent; 37 38 cout << "等待客戶端的連接" << endl; 39 if( !ConnectNamedPipe(hNamedPipe, &ovlpd) ) 40 { 41 if( ERROR_IO_PENDING != GetLastError() ) 42 { 43 CloseHandle(hNamedPipe); 44 CloseHandle(hEvent); 45 cout<<"等待客戶端連接失敗 ..."<< endl << endl; 46 return; 47 } 48 } 49 //等待事件 hEvent 失敗 50 if( WAIT_FAILED == WaitForSingleObject(hEvent, INFINITE) ) 51 { 52 CloseHandle(hNamedPipe); 53 CloseHandle(hEvent); 54 cout<<"等待對象失敗 ..."<<endl<<endl; 55 return; 56 } 57 CloseHandle(hEvent); 58 } 59 60 void NamePipeServer::NamedPipeReadInServer() 61 { 62 char * pReadBuf; 63 DWORD dwRead; 64 pReadBuf = new char[strlen(pStr) + 1]; 65 memset(pReadBuf, 0, strlen(pStr) + 1); 66 //從命名管道中讀取數據 67 if( !ReadFile(hNamedPipe, pReadBuf, strlen(pStr), &dwRead, NULL) ) 68 { 69 delete []pReadBuf; 70 cout<<"讀取數據失敗 ..."<< endl<< endl; 71 return; 72 } 73 cout << "讀取數據成功::"<< pReadBuf << endl<< endl; 74 } 75 76 void NamePipeServer::NamedPipeWriteInServer() 77 { 78 DWORD dwWrite; 79 //向命名管道中寫入數據 80 if( !WriteFile(hNamedPipe, pStr, strlen(pStr), &dwWrite, NULL) ) 81 { 82 cout << "寫入數據失敗 ..." << endl<< endl; 83 return; 84 } 85 cout << "寫入數據成功:: "<< pStr<< endl<< endl; 86 } 87 88 int main() 89 { 90 NamePipeServer pipeserver; 91 //創建命名管道 92 pipeserver.CreateNamedPipeInServer(); 93 //從命名管道讀數據 94 pipeserver.NamedPipeReadInServer(); 95 //向匿名管道中寫入數據 96 pipeserver.NamedPipeWriteInServer(); 97 98 system("pause"); 99 return 0; 100 }
客戶端代碼實現:
NamePipeClient.h
1 #ifndef _NAME_PIPE_CLIENT_H 2 #define _NAME_PIPE_CLIENT_H 3 4 #include<windows.h> 5 #include<iostream> 6 7 class NamePipeClient 8 { 9 public: 10 NamePipeClient() 11 { 12 pStr = "data from client"; 13 pPipeName = "\\\\.\\pipe\\testPipe"; 14 } 15 //打開命名管道 16 void OpenNamedPipeInClient(); 17 //客戶端從命名管道中讀取數據 18 void NamedPipeReadInClient(); 19 //客戶端往命名管道中寫入數據 20 void NamedPipeWriteInClient(); 21 22 private: 23 //用來保存在客戶端通過 CreateFile 打開的命名管道句柄HANDLE 24 HANDLE hNamedPipe; 25 const char * pStr; 26 const char * pPipeName; 27 }; 28 29 #endif
NamePipeClient.cpp
1 #include "stdafx.h" 2 #include "NamePipeClient.h" 3 4 using namespace std; 5 6 void NamePipeClient::OpenNamedPipeInClient() 7 { 8 //等待連接命名管道 9 if( !WaitNamedPipe(L"\\\\.\\pipe\\testspipe", NMPWAIT_WAIT_FOREVER) ) 10 { 11 cout<<"命名管道實例不存在 ..."<< endl<< endl; 12 return; 13 } 14 cout << "成功連接到服務器" << endl; 15 //打開命名管道 16 hNamedPipe = CreateFile( L"\\\\.\\pipe\\testspipe", GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); 17 if( INVALID_HANDLE_VALUE == hNamedPipe ) 18 { 19 cout << "打開命名管道失敗!!!" << endl << endl; 20 return; 21 } 22 } 23 24 void NamePipeClient::NamedPipeReadInClient() 25 { 26 char * pReadBuf; 27 DWORD dwRead; 28 pReadBuf = new char[strlen(pStr) + 1]; 29 memset(pReadBuf, 0, strlen(pStr) + 1); 30 //從命名管道中讀取數據 31 if( !ReadFile(hNamedPipe, pReadBuf, strlen(pStr), &dwRead, NULL) ) 32 { 33 delete []pReadBuf; 34 cout << "讀取數據失敗 ..."<< endl << endl; 35 return; 36 } 37 cout<<"讀取數據成功:: "<< pReadBuf << endl << endl; 38 } 39 40 void NamePipeClient::NamedPipeWriteInClient() 41 { 42 DWORD dwWrite; 43 //向命名管道中寫入數據 44 if( !WriteFile(hNamedPipe, pStr, strlen(pStr), &dwWrite, NULL) ) 45 { 46 cout<<"寫入數據失敗 ..." << endl << endl; 47 return; 48 } 49 cout<< "寫入數據成功:: "<< pStr << endl << endl; 50 } 51 52 int main() 53 { 54 NamePipeClient pipeclient; 55 pipeclient.OpenNamedPipeInClient(); 56 //往命名管道中寫入數據 57 pipeclient.NamedPipeWriteInClient(); 58 //接收從服務器發來的數據 59 pipeclient.NamedPipeReadInClient(); 60 system("pause"); 61 return 0; 62 }
首先啟動服務器,服務器在等待客戶端的連接
然后啟動客戶端
可以看到服務器成功從客戶端讀取到數據,並且寫入數據成功。
而客戶端也寫入服務器數據成功,並且成功讀取到服務器的數據。