接《win32內核對象共享和進程通信實例(一)》,先繼續了解一下windows匿名管道。
windows管道實質是一塊共享內存,可用於進程間通信。windows管道分為匿名管道和命名管道。匿名管道用戶本地進程間通信;命名管道可以用戶網絡通信。
匿名管道
①dos下的管道操作符|
最開始接觸管道這個概念是dos命令或者linux命令吧。比如dos下可以使用type test.txt|more實現對於test.txt的逐屏顯示。中間的“|”即為管道操作符,對其解釋是:把左邊的輸出作為右邊的輸入。
好好理解這句話,管道操作符“|”的作用是“把左邊的輸出作為右邊的輸入”,確切地來說,應該是把type test.txt的內容先輸出到管道,然后把管道的內容作為more命令的輸入(?這里可能有問題,但是先讓我這么認為)。
本來type test.txt默認是輸出到屏幕設備(執行type test.txt命令,它在屏幕打印出test.txt的內容),這個是它的標准輸出,使用管道操作符之后,把它的從原來的輸出到屏幕設備變成輸出到管道。這種改變原來標准輸入輸出方式的行為,也被稱為輸入輸出重定向。管道操作符是輸入輸出重定向的一種。一個重定向的簡單的例子是,執行 type test.txt>1.txt,這個就是把type test.txt的輸出在屏幕設備上變成輸出在1.txt文件中。重定向操作符參看下表:
重定向操作符 |
說明 |
> |
將命令輸出寫入到文件或設備(例如打印機)中,而不是寫在命令提示符窗口中 |
< |
從文件中而不是從鍵盤中讀入命令輸入 |
>> |
將命令輸出添加到文件末尾而不刪除文件中的信息 |
>& |
將前一個句柄的輸出寫成后一個句柄的輸入 |
<& |
從后一個句柄讀取輸入並寫入到前一個句柄輸出中 |
| |
從一個命令中讀取輸出並將其寫入另一個命令的輸入中。也稱作管道 |
應該是通過改變它們的輸入輸出句柄。可以這么認為,標准輸入輸出指的是設備,比如屏幕設備,鍵盤設備,通過輸入輸出句柄可以引用這些設備。如鍵盤輸入用0表示,窗口用1表示,具體可以參照下面的表格:
句柄 |
數值 |
說明 |
STDIN |
0 |
鍵盤輸入 |
STDOUT |
1 |
輸出到命令提示符窗口 |
STDERR |
2 |
錯誤輸出到命令提示符窗口 |
UNDEFINED |
3~9 |
程序自定義 |
①使用CreateNamedPipe函數創建一個命名管道實例,並且為隨后的管道操作返回一個句柄。一個命名管道服務進程調用該函數可以創建一個特定的命名管道的第一個實例,並設置它的基本屬性,也可以創建一個已經存在的命名管道的新的實例。
HANDLE CreateNamedPipe( LPCTSTR lpName, // pointer to pipe name DWORD dwOpenMode, // pipe open mode DWORD dwPipeMode, // pipe-specific modes DWORD nMaxInstances, // maximum number of instances DWORD nOutBufferSize, // output buffer size, in bytes DWORD nInBufferSize, // input buffer size, in bytes DWORD nDefaultTimeOut, // time-out time, in milliseconds LPSECURITY_ATTRIBUTES lpSecurityAttributes // pointer to security attributes );
第一個參數,指向管道名字字符串指針,字符串必須是如下格式:
\\.\pipe\pipename
第二個參數,管道打開模式,每一個管道的實例必須設置成相同的模式。當它設置為PIPE_ACCESS_DUPLEX,表示管道是雙向的,服務端和客戶端都可以從管道讀寫數據。
與此同時還可以設置FILE_FLAG_OVERLAPPED標識。表示開啟重疊模式,重疊模式開啟之后,那些可能需要花很多時間去操作的讀寫和連接操作能夠立即返回。該模式可以讓前台的線程把耗時的操作放在后台執行而去執行其他的操作。例如在重疊模式下,一個線程可以處理多個管道實例的同步輸入輸出(I/O)操作,或者在相同的管道上同步讀寫操作。如果沒有設置重疊模式,管道上的讀寫和連接操作要等操作完成之后才返回。
第三個參數,管道特性模式。指定管道句柄的類型,讀和寫。這對應命名管道的兩種通信模式,即字節模式和消息模式,第三個參數設置為PIPE_TYPE_BYTE表示使用字節模式通信;設置為PIPE_TYPE_MESSAGE表示使用消息模式通信。
第四個參數,指定管道最多可以創建多少個實例。所有實例必須指定相同的數目。可接受的值為1到PIPE_UNLIMITED_INSTANCES,如果設置為PIPE_UNLIMITED_INSTANCES,表示可根據系統資源的能力上限創建實例個數。
第五個參數,為輸出緩存預留字節數。
第六個參數,為輸入緩存預留的字節數。
第七個參數,指定一個超時值,以毫秒為單位,如果后面使用WaitNamedPipe指定了NMPWAIT_USE_DEFAULT_WAIT,每個實例必須設置相同的值。
第八個參數,指向安全屬性的結構。主要用來確認創建的管道對象能否被子進程繼承。
②使用ConnectNamedPipe去等待一個客戶端進程連接一個命名管道的實例。
BOOL ConnectNamedPipe( HANDLE hNamedPipe, // handle to named pipe to connect LPOVERLAPPED lpOverlapped // pointer to overlapped structure );
第一個參數,命名管道實例的句柄,即CreateNamedPipi的返回值。
第二個參數,指向OVERLAPPED結構體。
如果在CreateNamedPipe的時候設置了FILE_FLAG_OVERLAPPED標識,即開啟了重疊模式,這里就要傳入一個OVERLAPPED的結構體指針,同時OVERLAPPED結構體必須包含一個人工重置對象的事件句柄(MSDN上是這么說的)。
OVERLAPPED結構體包含了用於異步輸入/輸出(I/0)的信息。這里只用到最后一個成員即hEvent。
typedef struct _OVERLAPPED { // o DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED;
這個過程是怎么樣的呢?我們首先用CreateEvent創建一個人工重置事件,並設置成無信號,並將返回的event對象句柄賦值給OVERLAPPED結構體的hEvent,這樣一來,有客戶端連接管道實例的時候,event就會變成有信號狀態。我們使用WaitForSingleObject等待event變為有信號狀態,然后讓程序返回。
之前有說過,管道的等待連接操作是一個耗時操作,如果不設置為重疊模式,就會一直等待,服務端也會阻塞。當然也可以創建一個新線程,讓程序做別的事。
③使用ReadFile和WriteFile讀寫數據。
編寫命名管道客戶端程序
①使用WaitNamedPipe等待一個指定的可連接的命名管道(也就是,管道的服務端進程有一個在等待的ConnectNamedPipe操作),直到超時
BOOL WaitNamedPipe( LPCTSTR lpNamedPipeName, // pointer to name of pipe for which to wait DWORD nTimeOut // time-out interval, in milliseconds );
第一個參數,指向在等待連接的管道的名稱,要使用這樣的格式:
\\servername\pipe\pipename
如果服務端在本地,那么servername設置為.
第二個參數,設置以毫秒為單位的超時。除了使用毫秒數字之外,還可以使用下面兩個值:
NMPWAIT_USE_DEFAULT_WAIT,表示使用服務端CreateNamePipe指定的超時值。
NMPWAIT_WAIT_FOREVER,如果沒有管道可用那就一直等待。
②使用CreateFile打開管道
HANDLE CreateFile( LPCTSTR lpFileName, // pointer to name of the file DWORD dwDesiredAccess, // access (read-write) mode DWORD dwShareMode, // share mode LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes DWORD dwCreationDisposition, // how to create DWORD dwFlagsAndAttributes, // file attributes HANDLE hTemplateFile // handle to file with attributes to // copy );
最后完成一個實例吧:
服務端代碼
#include<windows.h> #define IDB_CREATE 1020 #define IDB_WRITE 1021 #define IDB_READ 1022 HINSTANCE hInstance; LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){ hInstance=hInstance; WNDCLASS wc; wc.cbClsExtra=0; wc.cbWndExtra=0; wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance; wc.lpfnWndProc=WndProc; wc.lpszClassName="NpipServ"; wc.lpszMenuName=NULL; wc.style=CS_HREDRAW|CS_VREDRAW; if(!RegisterClass(&wc)){ MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR); return 0; } HWND hwnd=CreateWindow("NpipServ","NamedPipe Server",WS_OVERLAPPEDWINDOW,300,300,350,150,NULL,NULL,hInstance,NULL); ShowWindow(hwnd,nShowCmd); UpdateWindow(hwnd); MSG msg; while(GetMessage(&msg,NULL,0,0)){ TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){ HDC hdc; PAINTSTRUCT ps; static HANDLE hPipe; switch(message){ case WM_CREATE: CreateWindow("Button","創建管道",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_CREATE,hInstance,NULL); CreateWindow("Button","寫入數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL); CreateWindow("Button","讀取數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,230,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL); return 0; case WM_PAINT: hdc=BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps); return 0; case WM_COMMAND: switch(LOWORD(wParam)){ case IDB_CREATE: { //創建命名管道 hPipe=CreateNamedPipe("\\\\.\\pipe\\NamedPipeForTest",PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE,1,1024,1024,0,NULL); if(INVALID_HANDLE_VALUE==hPipe){ MessageBox(hwnd,"Fail to Create Namedpipe","",MB_ICONERROR); return 0; } // HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent){ CloseHandle(hPipe); MessageBox(hwnd,"Fail to Create Event","",MB_ICONERROR); return 0; } OVERLAPPED ov; ov.hEvent=hEvent; //等待連接 if(!ConnectNamedPipe(hPipe,&ov)){ if(ERROR_IO_PENDING!=GetLastError()){ CloseHandle(hPipe); CloseHandle(hEvent); MessageBox(hwnd,"Fail to wait a client connect","",MB_ICONERROR); return 0; } } WaitForSingleObject(hEvent,INFINITE); return 0; } case IDB_WRITE: { //寫入 TCHAR writeBuff[]="The Sky Becomes lively Rise...(from server)"; DWORD dwWrite; if(!WriteFile(hPipe,writeBuff,strlen(writeBuff),&dwWrite,NULL)){ MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR); return 0; } return 0; } case IDB_READ: { //讀取數據 TCHAR readBuff[100]; ZeroMemory(readBuff,sizeof(readBuff)); DWORD dwRead; if(!ReadFile(hPipe,readBuff,100,&dwRead,NULL)){ MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR); return 0; }else{ MessageBox(hwnd,readBuff,"msg",MB_OK); } return 0; } } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); }
客戶端代碼
#include<windows.h> #define IDB_CONNECT 1030 #define IDB_WRITE 1031 #define IDB_READ 1032 HINSTANCE hInstance; LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){ hInstance=hInstance; WNDCLASS wc; wc.cbClsExtra=0; wc.cbWndExtra=0; wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance; wc.lpfnWndProc=WndProc; wc.lpszClassName="NpipeClient"; wc.lpszMenuName=NULL; wc.style=CS_HREDRAW|CS_VREDRAW; if(!RegisterClass(&wc)){ MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR); return 0; } HWND hwnd=CreateWindow("NpipeClient","NamedPipe Client",WS_OVERLAPPEDWINDOW,700,300,350,150,NULL,NULL,hInstance,NULL); ShowWindow(hwnd,nShowCmd); UpdateWindow(hwnd); MSG msg; while(GetMessage(&msg,NULL,0,0)){ TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){ HDC hdc; PAINTSTRUCT ps; static HANDLE hPipe; switch(message){ case WM_CREATE: CreateWindow("Button","連接管道",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_CONNECT,hInstance,NULL); CreateWindow("Button","寫入數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL); CreateWindow("Button","讀取數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,230,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL); return 0; case WM_PAINT: hdc=BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps); return 0; case WM_COMMAND: switch(LOWORD(wParam)){ case IDB_CONNECT: { //連接管道 if(!WaitNamedPipe("\\\\.\\pipe\\NamedPipeForTest",NMPWAIT_WAIT_FOREVER)){ MessageBox(hwnd,"Fail to connect namedpipe","",MB_ICONERROR); return 0; } //打開管道 hPipe=CreateFile("\\\\.\\pipe\\NamedPipeForTest",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hPipe){ MessageBox(hwnd,"Fail to open namedpipe","",MB_ICONERROR); return 0; } return 0; } case IDB_WRITE: { //寫入 TCHAR writeBuff[]="A Bird Fly To Sky..(from client)"; DWORD dwWrite; if(!WriteFile(hPipe,writeBuff,strlen(writeBuff),&dwWrite,NULL)){ MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR); return 0; } return 0; } case IDB_READ: { //讀取數據 TCHAR readBuff[100]; ZeroMemory(readBuff,sizeof(readBuff)); DWORD dwRead; if(!ReadFile(hPipe,readBuff,100,&dwRead,NULL)){ MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR); return 0; }else{ MessageBox(hwnd,readBuff,"msg",MB_OK); } return 0; } } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); }
運行
推薦