Windows下用管道通信(pipe)實現進程間數據共享
管道是一種用於在進程間共享數據的機制,其實質是一段共享內存。Windows系統為這段共享的內存設計采用數據流I/0的方式來訪問。由一個進程讀、另一個進程寫,類似於一個管道兩端,因此這種進程間的通信方式稱作“管道”。
管道分為匿名管道和命名管道。
匿名管道只能在父子進程間進行通信,不能在網絡間通信,而且數據傳輸是單向的,只能一端寫,另一端讀。
命令管道可以在任意進程間通信,通信是雙向的,任意一端都可讀可寫,但是在同一時間只能有一端讀、一端寫。
創建匿名管道:
1. 定義安全屬性結構體:
SECURITY_ATTRIBUTES sa; sa.bInheritHandle = TRUE;//表示可被子進程所繼承 sa.lpSecurityDescriptor = NULL; //安全描述符號一般都設置成NULL,即默認描述符 sa.nLength = sizeof(SECURITY_ATTRIBUTES); //管道長度
其中SECURITY_ATTRIBUTES結構體的定義為:
typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
2. 創建管道:
BOOL WINAPI CreatePipe( PHANDLE hReadPipe,//讀取端句柄 PHANDLE hWritePipe,//輸入端句柄 LPSECURITY_ATTRIBUTES lpPipeAttributes,//安全屬性 DWORD nSize// 管道的緩沖區容量,NULL表示默認大小 );
3. 讀取管道內數據:
BOOL ReadFile( HANDLE hFile,//句柄,可以是標准輸入輸出流或文件或管道 LPVOID lpBuffer, //讀取的數據寫入緩沖區 DWORD nNumberOfBytesToRead,//指定讀取的字節數 LPDWORD lpNumberOfBytesRead,//實際讀取的字節數 LPOVERLAPPED lpOverlapped//用於異步操作,一般置為NULL );
4. 向管道內寫入數據:
BOOL WriteFile( HANDLE hFile,//句柄,同上 LPCVOID lpBuffer,//指定待寫入的數據 DWORD nNumberOfBytesToWrite,//寫入的數據量 LPDWORDlp NumberOfBytesWritten,//實際要寫的數據量 LPOVERLAPPED lpOverlapped//一般置為NULL );
5. 為實現父子進程間的通信,需要對子進程的管道進行重定向:
我們知道創建子進程函數 CreateProcess中有一個參數STARUIINFO,默認情況下子進程的輸入輸出管道是標准輸入輸出流,可以通過下面的方法實現管道重定向:
STARTUPINFO si; si.hStdInput = hPipeInputRead; //輸入由標准輸入 -> 從管道中讀取 si.hStdOutput = hPipeOutputWrite; //輸出由標准輸出 -> 輸出到管道
創建命名管道:
命名管道有點類似我們常聽見的服務器端和客戶端,管道正好起着傳輸正如他的名字,命名管道有自己的名字,首先要指定管道名,管道名遵循的格式為:
\\.\pipe\pipename。最多可達256個字符的長度,而且不區分大小寫
例如:"\\\\.\\pipe\\Name_pipe_demon_get"
服務器端創建命名管道
HANDLE WINAPI CreateNamedPipe( LPCTSTRlpName,//管道名 DWORD dwOpenMode,//管道打開方式 //PIPE_ACCESS_DUPLEX 該管道是雙向的,服務器和客戶端進程都可以從管道讀取或者向管道寫入數據。 //PIPE_ACCESS_INBOUND 該管道中數據是從客戶端流向服務端,即客戶端只能寫,服務端只能讀。 //PIPE_ACCESS_OUTBOUND 該管道中數據是從服務端流向客戶端,即客戶端只能讀,服務端只能寫。 DWORD dwPipeMode,//管道的模式 //PIPE_TYPE_BYTE 數據作為一個連續的字節數據流寫入管道。 //PIPE_TYPE_MESSAGE 數據用數據塊(名為“消息”或“報文”)的形式寫入管道。 //PIPE_READMODE_BYTE 數據以單獨字節的形式從管道中讀出。 //PIPE_READMODE_MESSAGE 數據以名為“消息”的數據塊形式從管道中讀出(要求指定PIPE_TYPE_MESSAGE)。 //PIPE_WAIT 同步操作在等待的時候掛起線程。 //PIPE_NOWAIT 同步操作立即返回。 DWORD nMaxInstances,//表示該管道所能夠創建的最大實例數量。必須是1到常數PIPE_UNLIMITED_INSTANCES(255)間的一個值。 DWORD nOutBufferSize,//表示管道的輸出緩沖區容量,為0表示使用默認大小。 DWORD nInBufferSize,//表示管道的輸入緩沖區容量,為0表示使用默認大小。 DWORD nDefaultTimeOut,//表示管道的默認等待超時。 LPSECURITY_ATTRIBUTES lpSecurityAttributes//表示管道的安全屬性。 );
創建完成后等待連接
BOOL WINAPI ConnectNamedPipe( HANDLE hNamedPipe,//命名管道句柄 LPOVERLAPPED lpOverlapped//一般為NULL );
服務器端就緒后,客戶端開始連接
BOOL WINAPI WaitNamedPipe( LPCTSTR lpNamedPipeName,//命名管道名稱 DWORD nTimeOut//等待時長 );
連接成功后,打開管道進行數據通信,使用CreateFile,ReadFile和WriteFile,前面匿名管道已經給出了具體使用方法。
下面來看一個具體的例子
A程序作為服務器,不斷從B程序接收數據,並發送到C程序中:
#include <stdio.h> #include <conio.h> #include <tchar.h> #include <Windows.h> #include <process.h> #include <stdlib.h> const char *pStrPipeNameGet = "\\\\.\\pipe\\Name_pipe_demon_get"; const char *pStrPipeNameSend = "\\\\.\\pipe\\Name_pipe_demon_send"; const int BUFFER_MAX_LEN = 1024; char buf[BUFFER_MAX_LEN]; DWORD dwLen; HANDLE get, mSend, mutex; LPCRITICAL_SECTION cs; WCHAR* toWChar(const char *c){ WCHAR wszClassName[256]; memset(wszClassName, 0, sizeof(wszClassName)); MultiByteToWideChar(CP_ACP, 0, c, strlen(c) + 1, wszClassName, sizeof(wszClassName) / sizeof(wszClassName[0])); return wszClassName; } void beginGetThread(PVOID p){ printf("服務器Get\n"); printf("等待連接......\n"); HANDLE hPipe = CreateNamedPipe(toWChar(pStrPipeNameGet), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, 0); if (ConnectNamedPipe(hPipe, NULL) != NULL)//等待連接。 { printf("連接成功,開始接收數據\n"); while (true) { WaitForSingleObject(mutex, INFINITE); EnterCriticalSection(cs); //接收客戶端發送的數據 ReadFile(hPipe, buf, BUFFER_MAX_LEN, &dwLen, NULL); printf("接收到來自A的數據長度為%d字節\n", dwLen); printf("具體數據內容如下:"); int bufSize; for (bufSize = 0; bufSize < (int)dwLen; bufSize++){ putchar(buf[bufSize]); } LeaveCriticalSection(cs); Sleep(500); ReleaseSemaphore(mutex, 1, NULL); putchar('\n'); } } else { printf("連接失敗\n"); } DisconnectNamedPipe(hPipe); CloseHandle(hPipe);//關閉管道 } void beginSendThread(PVOID p){ printf("服務器Send\n"); printf("等待連接......\n"); HANDLE hPipe = CreateNamedPipe(toWChar(pStrPipeNameSend), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, 0); if (ConnectNamedPipe(hPipe, NULL) != NULL)//等待連接。 { printf("連接成功,開始發送緩沖區數據至B\n"); while (true) { WaitForSingleObject(mutex, INFINITE); EnterCriticalSection(cs); WriteFile(hPipe, buf, (int)dwLen, &dwLen, NULL); LeaveCriticalSection(cs); Sleep(500); ReleaseSemaphore(mutex, 1, NULL); } } else { printf("連接失敗\n"); } DisconnectNamedPipe(hPipe); CloseHandle(hPipe);//關閉管道 } int _tmain(int argc, _TCHAR* argv[]) { cs = (LPCRITICAL_SECTION)malloc(sizeof(LPCRITICAL_SECTION)); InitializeCriticalSection(cs); mutex = CreateSemaphore(NULL, 1, 1, TEXT("mutex")); _beginthread(beginGetThread, NULL, NULL); _beginthread(beginSendThread, NULL, NULL); Sleep(INFINITE); DeleteCriticalSection(cs); return 0; }
B程序不斷接收從鍵盤輸入的數據,數據以回車結束,並發送給A
#include <stdio.h> #include <tchar.h> #include <Windows.h> #include <conio.h> const char *pStrPipeName = "\\\\.\\pipe\\Name_pipe_demon_get"; const int BUFFER_MAX_LEN = 1024; char buf[BUFFER_MAX_LEN]; int _tmain(int argc, _TCHAR* argv[]) { printf("按任意鍵以開始連接Get\n"); _getch(); printf("A開始等待......\n"); if (!WaitNamedPipe(pStrPipeName, NMPWAIT_WAIT_FOREVER)) { printf("Error! 連接Get失敗\n"); return 0; } HANDLE hPipe = CreateFile(pStrPipeName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); while (true) { printf("請輸入要向服務端發送的數據,回車鍵結束,最大1024個字節\n"); DWORD dwLen = 0; int bufSize; for (bufSize = 0; bufSize < BUFFER_MAX_LEN; bufSize++){ buf[bufSize] = getchar(); if (buf[bufSize] == '\n') break; } //向服務端發送數據 if (WriteFile(hPipe, buf, bufSize, &dwLen, NULL)){ printf("數據寫入完畢共%d字節\n", dwLen); } else { printf("數據寫入失敗\n"); } Sleep(1000); } CloseHandle(hPipe); return 0; }
C程序接收到從A發送來的數據,並轉換成大寫寫入文件
#include <stdio.h> #include <tchar.h> #include <Windows.h> #include <conio.h> const char *pStrPipeName = "\\\\.\\pipe\\Name_pipe_demon_send"; const int BUFFER_MAX_LEN = 1024; char buf[BUFFER_MAX_LEN]; DWORD dwLen = 0; int _tmain(int argc, _TCHAR* argv[]) { printf("按任意鍵以開始連接Send\n"); _getch(); printf("B開始等待......\n"); if (!WaitNamedPipe(pStrPipeName, NMPWAIT_WAIT_FOREVER)) { printf("Error! 連接Send失敗\n"); return 0; } HANDLE hPipe = CreateFile(pStrPipeName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); while (true) { // 接收服務端發回的數據 ReadFile(hPipe, buf, BUFFER_MAX_LEN, &dwLen, NULL);//讀取管道中的內容(管道是一種特殊的文件) printf("接收服務端發來的信息,長度為%d字節\n", dwLen); printf("具體數據內容如下:"); HANDLE hWrite = CreateFile(_T("data.txt"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); for (int j = 0; j <= (int )dwLen; j++){ putchar(buf[j]); buf[j] = toupper(buf[j]); } putchar('\n'); SetFilePointer(hWrite, NULL, NULL, FILE_END); WriteFile(hWrite, buf, (int)dwLen, NULL, NULL); WriteFile(hWrite, "\n", 1, NULL, NULL); CloseHandle(hWrite); Sleep(1000); } CloseHandle(hPipe); return 0; }