一、進程與進程通信
進程間通信(Interprocess Communication, IPC)是指不同的進程之間進行數據共享和數據交換。
二、進程間通信方式
1. 文件映射
注:文件映射是在多個進程間共享數據的非常有效方法,有較好的安全性。但文件映射只能用於本地機器的進程之間,不能用於網絡中,而開發者還必須控制進程間的同步。
使用內存映射文件的一般流程:(reference from: https://blog.csdn.net/qq_20183489/article/details/54646794)

另外,內存映射文件在處理大數據量的文件時表現出了良好的性能(實際上,文件越大,內存映射的優勢就越明顯)。示例可以參考:https://blog.csdn.net/zzq060143/article/details/54619571
2.2 共享內存
Win32 API中共享內存(Shared Memory)實際就是文件映射的一種特殊情況。進程在創建文件映射對象時用0xFFFFFFFF(INVALID_HANDLE_VALUE)來代替文件句柄(HANDLE),就表示了對應的文件映射對象是從操作系統頁面文件訪問內存,其它進程打開該文件映射對象就可以訪問該內存塊。由於共享內存是用文件映射實現的,所以它也有較好的安全性,也只能運行於同一計算機上的進程之間。
共享內存實現數據共享示例如下:
1 //發送數據的進程先啟動,用於發送數據,即將數據寫入視圖 。 2 3 #include "stdafx.h" 4 #include <Windows.h> 5 #include <conio.h> 6 7 #define BUFFER_SIZE 256 8 TCHAR szMapFileName[] = TEXT("MyFileMappingName"); //映射文件名,即共享內存的名稱 9 TCHAR szSendData[] = TEXT("Message from the send process."); 10 11 int main() 12 { 13 HANDLE hMapFile = NULL; 14 LPCTSTR pBuf = NULL; 15 16 //1. 創建一個文件映射內核對象 17 hMapFile = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUFFER_SIZE, szMapFileName); //INVALID_HANDLE_VALUE表示創建一個進程間共享的對象 18 if (NULL == hMapFile) 19 { 20 _tprintf(TEXT("Could not create file mapping object (%d).\n"), GetLastError()); 21 return -1; 22 } 23 24 //2. 將文件數據映射到進程的地址空間 25 pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE); 26 if (NULL == pBuf) 27 { 28 _tprintf(TEXT("Could not map view of file (%d). \n"), GetLastError()); 29 30 CloseHandle(hMapFile); 31 hMapFile = NULL; 32 33 return -1; 34 } 35 36 //3. 寫入到內存中 37 CopyMemory((void*)pBuf, szSendData, _tcslen(szSendData) * sizeof(TCHAR)); 38 _getch(); //這個函數是一個不回顯函數,當用戶按下某個字符時,函數自動讀取,無需按回車 39 40 //4. 從進程的地址空間中撤消文件數據的映像 41 UnmapViewOfFile(pBuf); 42 43 //5. 關閉文件映射對象和文件對象 44 CloseHandle(hMapFile); 45 46 getchar(); 47 return 0; 48 }
1 //接收數據的進程后啟動,用於接收數據,即讀取視圖的數據 2 3 #include "stdafx.h" 4 #include <Windows.h> 5 6 #define BUFFER_SIZE 256 7 TCHAR szMapFileName[] = TEXT("MyFileMappingName"); 8 9 int main() 10 { 11 HANDLE hMapFile = NULL; 12 LPCTSTR pBuf = NULL; 13 14 //1. 打開一個命名的文件映射內核對象 15 hMapFile = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, szMapFileName); 16 if (NULL == hMapFile) 17 { 18 _tprintf(TEXT("Could not open file mapping object (%d).\n"), GetLastError()); 19 return -1; 20 } 21 22 //2. 將文件映射內核對象hFileMapping映射到當前應用程序的進程地址pBuf,通過該指針可以讀寫共享的內存區域 23 pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE); 24 if (NULL == pBuf) 25 { 26 _tprintf(TEXT("Could not map view of file (%d). \n"), GetLastError()); 27 28 CloseHandle(hMapFile); 29 hMapFile = NULL; 30 31 return -1; 32 } 33 34 //3. 顯示接收到的數據 35 for (int i = 0; i < _tcsclen(pBuf); i++) 36 { 37 _tprintf(TEXT("%c"), *(pBuf + i)); 38 } 39 printf("\n"); 40 41 //4. 從進程的地址空間中撤消文件數據的映像 42 UnmapViewOfFile(pBuf); 43 44 //5. 關閉文件映射對象和文件對象 45 CloseHandle(hMapFile); 46 47 getchar(); 48 return 0; 49 }
2.3 管道
管道(Pipe)是一種具有兩個端點的通信通道:有一端句柄的進程可以和有另一端句柄的進程通信。管道可以是單向-一端是只讀的,另一端點是只寫的;也可以是雙向的-管道的兩端點既可讀也可寫。
(1) 匿名管道(Anonymous Pipe)是在父進程和子進程之間,或同一父進程的兩個子進程之間傳輸數據的無名字的單向管道。通常由父進程創建管道,然后由要通信的子進程繼承通道的讀端點句柄或寫 端點句柄,然后實現通信。父進程還可以建立兩個或更多個繼承匿名管道讀和寫句柄的子進程。這些子進程可以使用管道直接通信,不需要通過父進程。
匿名管道是單機上實現子進程標准I/O重定向的有效方法,它不能在網上使用,也不能用於兩個不相關的進程之間。
匿名管道通信過程:
>>父進程讀寫過程
①創建匿名管道
②創建子進程,並對子進程相關數據進行初始化(用匿名管道的讀取/寫入句柄賦值給子進程的輸入/輸出句柄)。
③關閉子進程相關句柄。(進程句柄,主線程句柄)
④對管道讀寫
>>子進程讀寫過程
①獲得輸入輸出句柄
②對管道讀寫
相關函數:
CreatePipe 管道創建
函數原型
BOOL CreatePipe( PHANDLE hReadPipe, // pointer to read handle PHANDLE hWritePipe, // pointer to write handle LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes DWORD nSize // pipe size );
參數說明:
-
-
- hReadPipe 作為返回類型使用,返回管道讀取句柄
- hWritePipe 作為返回類型使用,返回管道寫入句柄
- lpPipeAttributes 指向SECURITY_ATTRIBUTES結構體的指針,檢測返回句柄是否能被子進程繼承,如果此參數為NULL,則句柄不能被繼承
- nSize 指定管道的緩沖區大小,改大小只是個建議值,系統將用這個值來計算一個適當的緩存區大小,如果此參數是0,系統會使用默認的緩沖區大小
-
返回值
若函數成功返回非零值
若函數失敗返回0,詳細消息可以調用GetLastError函數獲得
1 // 父進程.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <iostream> 7 using namespace std; 8 9 int main() 10 { 11 const int nBufferLen = 256; 12 13 SECURITY_ATTRIBUTES sa; 14 HANDLE hRead = NULL; 15 HANDLE hWrite = NULL; 16 17 STARTUPINFO sui; 18 PROCESS_INFORMATION pi; 19 20 char szBuffer[nBufferLen] = { 0 }; 21 DWORD dwReadLen = 0; 22 23 BOOL bRet = FALSE; 24 25 //1. 創建匿名管道 26 sa.bInheritHandle = TRUE; 27 sa.lpSecurityDescriptor = NULL; 28 sa.nLength = sizeof(SECURITY_ATTRIBUTES); 29 bRet = ::CreatePipe(&hRead, &hWrite, &sa, 0); 30 if (!bRet) 31 { 32 cout << "創建匿名管道失敗!" << endl; 33 system("pause"); 34 return -1; 35 } 36 37 //2. 創建子進程,並對子進程相關數據進行初始化(用匿名管道的讀取寫入句柄賦予子進程的輸入輸出句柄) 38 ZeroMemory(&sui, sizeof(STARTUPINFO)); 39 sui.cb = sizeof(STARTUPINFO); 40 sui.dwFlags = STARTF_USESTDHANDLES; 41 sui.hStdInput = hRead; 42 sui.hStdOutput = hWrite; 43 sui.hStdError = GetStdHandle(STD_ERROR_HANDLE); 44 bRet = ::CreateProcess(L"..\\x64\\Debug\\子進程.exe", NULL, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &sui, &pi); 45 if (!bRet) 46 { 47 cout << "創建子進程失敗!" << endl; 48 system("pause"); 49 return -1; 50 } 51 52 //3. 關閉子進程相關句柄(進行句柄,進程主線程句柄) 53 CloseHandle(pi.hProcess); 54 CloseHandle(pi.hThread); 55 56 Sleep(2000); 57 58 //4. 讀取數據 59 bRet = ::ReadFile(hRead, szBuffer, nBufferLen, &dwReadLen, NULL); 60 if (!bRet) 61 { 62 cout << "讀取數據失敗!" << endl; 63 system("pause"); 64 return -1; 65 } 66 67 cout << "從子進程接收到到數據: " << szBuffer << endl; 68 69 system("pause"); 70 return 0; 71 }
1 // 子進程.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <iostream> 7 using namespace std; 8 9 int main() 10 { 11 HANDLE hRead = NULL; 12 HANDLE hWrite = NULL; 13 14 BOOL bRet = FALSE; 15 16 //1. 獲得匿名管道輸入輸出句柄 17 hRead = GetStdHandle(STD_INPUT_HANDLE); 18 hWrite = GetStdHandle(STD_OUTPUT_HANDLE); 19 20 char szSendBuffer[] = "子進程寫入管道成功!"; 21 DWORD dwWriteLen = 0; 22 23 //2. 寫入數據 24 bRet = WriteFile(hWrite, szSendBuffer, (DWORD)strlen(szSendBuffer), &dwWriteLen, NULL); 25 if (!bRet) 26 { 27 system("pause"); 28 return -1; 29 } 30 31 Sleep(500); 32 33 system("pause"); 34 return 0; 35 }
(2) 命名管道(Named Pipe)是服務器進程和一個或多個客戶進程之間通信的單向或雙向管道。命名管道可以在不相關的進程之間和不同計算機之間使用,服務器建立命名管道時給它指定一個名字,任何進程都可以通過該名字打開管道的另一端,根據給定的權限和服務器進程通信。
命名管道提供了相對簡單的編程接口,使通過網絡傳輸數據並不比同一計算機上兩進程之間通信更困難,不過如果要同時和多個進程通信它就力不從心了。
命名管道有兩種通信方式:
A. 字節模式:在字節模式下,數據以一個連續的字節流的形式在客戶機和服務器之間流動
B. 消息模式:在消息模式下,客戶機和服務器則通過一系列不連續的數據單位,進行數據收發,每次在管道上發一條消息后,它必須作為一條完整的消息讀入
通信流程:
服務器端: 創建命名管道 -> 服務器等待用戶連接 -> 讀寫數據
客戶端:連接命名管道 -> 打開命名管道 -> 讀寫數據
相關函數:
顯示相關函數 [CreateNamePine 創建命名管道][ConnectNamePipe 創建連接命名管道][WaitNamedPipe 進行命名管道連接] CreateNamePipe 創建命名管道 函數原型: HANDLE CreateNamedPipe( LPCTSTR lpName, // pipe name DWORD dwOpenMode, // pipe open mode DWORD dwPipeMode, // pipe-specific modes DWORD nMaxInstances, // maximum number of instances DWORD nOutBufferSize, // output buffer size DWORD nInBufferSize, // input buffer size DWORD nDefaultTimeOut, // time-out interval LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD ); 參數說明: lpName 一個指向空終止的字符串,該字符串的格式必須是:"\\.\pine\pinename"其中該字符串開始是兩個連續的反斜杠,其后的原點表示是本地機器,如果想要與遠程的服務器建立連接連接,那么在原點這個位置應該指定這個遠程服務器的名稱.接下來是"pine"這個固定的字符串,也就是說這個字符串的內容不能修改,但其大小寫是無所謂的,最后是所創建的命名管道的名稱 dwOpenMode 指定管道的訪問方式,重疊方式.寫直通方式,還有管道句柄的安全訪問方式() 用來指定管道的訪問方式的標志取值如下(下面這三個值只能夠取其中一個),並且管道的每一個實例都必須具有同樣的訪問方式 PIPE_ACCESS_INBOUND 管道只能用作接收數據(服務器只能讀數據,客戶端只能寫數據),相當於在CreateFile中指定了GENERIC_READ PIPE_ACCESS_OUTBOUND 管道只能用作發送數據(服務器只能寫數據,客戶端只能讀數據),相當於在CreateFile中指定了GENERIC_WRITE PIPE_ACCESS_DUPLEX 管道既可以發送也可以接收數據,相當於在CreateFile中指定了GENERIC_READ | GENERIC_WRITE 用來指定寫直通方式和重疊方式的標志,取值可以是一下一個或多個組合 FILE_FLAG_WRITE_THROUGH 管道用於同步發送和接收數據,只有在數據被發送到目標地址時發送函數才會返回,如果不設置這個參數那么在系統內部對於命名管道的處理上可能會因為減少網絡附和而在數據積累到一定量時才發送,並且對於發送函數的調用會馬上返回 FILE_FLAG_OVERLAPPED 管道可以用於異步輸入和輸出,異步讀寫的有關方法和文件異步讀寫是相同的 用來指定管道安全訪問方式的標志,取值可以是一下一個或多個組合 WRITE_DAC 調用者對命名管道的任意范圍控制列表(ACL)都可以進行寫入訪問 WRITE_OWNER 調用者對命名管道的所有者可以進行寫入訪問 ACCESS_SYSTEM_SECURITY 調用者對命名管道打安全范圍控制列表(SACL)可以進行寫入訪問 dwPipeMode 指定管道類型,,讀取和等待方式可以是下面值的組合(0為字節寫字節讀阻塞方式) 用於指定管道句柄的寫入的標志 PIPE_TYPE_BYTE 數據在通過管道發送時作為字節流發送,不能與PIPE_READMODE_MESSAGE共用 PIPE_TYPE_MESSAGE 數據在通過管道發送時作為消息發送,不能與PIPE_READMODE_BYTE共用 用於指定管道句柄的讀取方式 PIPE_READMODE_BYTE 在接收數據時接收字節流該方式在PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE類型均可以使用 PIPE_READMODE_MESSAGE 在接收數據時接收消息該方式只用在PIPE_TYPE_MESSAGE類型下才可以使用 用於指定管道句柄的等待方式(同一管道的不同實例可以采取不同的等待方式) PIPE_WAIT 使用等待模式(阻塞方式),在讀,寫和建立連接時都需要管道的另一方完成相應動作后才會返回 PIPE_NOWAIT 使用非等待模式(非阻塞方式),在讀,寫和建立連接時不需要管道的另一方完成相應動作后就會立即返回 nMaxInstances 為管道的的最大數量,在第一次建立服務器方管道時這個參數表明該管道可以同時存在的數量。PIPE_UNLIMITED_INSTANCES表明不對數量進行限制 nOutBufferSize 表示輸出緩沖區的大小 nInBufferSize 表示輸入緩沖區的大小 nDefaultTimeOut 表示在等待連接時最長的等待時間(以毫秒為單位),如果在創建時設置為NMPWAIT_USE_DEFAULT_WAIT表明無限制的等待,而以后服務器方的其他管道實例也需要設置相同的值 lpSecurityAttributes 為安全屬性,一般設置為NULL。如果創建或打開失敗則返回INVALID_HANDLE_VALUE。可以通過GetLastError得到錯誤 返回值 若函數成功,返回值是一個命名通道實例的句柄,如果命名通道已經存在則返回一個以存在的命名通道的句柄,並調用GetLastError函數的返回值為 ERROR_ALREADY_EXISTS 若函數失敗,返回值為INVALID_HANDLE_VALUE若想獲得更多信息調用GetLastError函數獲得 ConnectNamePipe 創建命名管道連接 函數原型 BOOL ConnectNamedPipe( HANDLE hNamedPipe, // handle to named pipe LPOVERLAPPED lpOverlapped // overlapped structure ); 參數說明: lpNamedPipe : 指向一個命名管道實例的服務的句柄,該句柄由CreateNamedPipe函數返回 lpOverlapped: 指向一個OVERLAPPED結構的指針,如果hNamedPipe參數所標識的管道是用FILE_FLAG_OVERLAPPED標志打開的,則這個參數不能是NULL,必須是一個有效的指向一個OVERLAPPED的結構指針;否則函數則會錯誤的執行.如果hNampdPipe參數標志的管道用FILE_FLAG_OVERLAPPED標志打開的,並且這個參數不是NULL,則這個OVERLAPPED結構體必須包含人工重置對象句柄. 返回值 如果函數成功返回非零值如果失敗返回0詳細消息可以調用GetLastError函數獲得 WaitNamedPipe 進行命名管道連接 函數原型 BOOL WaitNamedPipe( LPCTSTR lpNamedPipeName, // pipe name DWORD nTimeOut // time-out interval ); 參數說明: lpNamedPipeName 用來指定管道的名稱,這個名稱必須包括創建該命名管道的服務器進程所在的機器的名稱,該名稱的格式必須是"\\.\pine\pinename".如果在同一台機器上編寫的命名管道的服務器端程序和客戶端程序,則應該指定這個名稱時,在開始的兩個反斜桿后可以設置一個圓點,表示服務器進程在本地機器上運行;如果是跨網絡通信,則在這個圓點位置處應該指定服務器端程序所在的主機名 nTimeOut 指定超時間隔. NMPWAIT_USE_DEFAULT_WAIT 超時間隔就是服務器端創建該命名管道時指定的超時值 NWPWAIT_WAIT_FOREVER 一直等待,直到出現了一個可用的命名管道的實例 也就是說,如果這個參數的值是NMPWAIT_USE_DEFAULT_WAIT,並且在服務器端調用CreateNamedPipe函數創建命名管道時,設置的超時間隔為1000ms,那么一個命名管道的所有實例來說,它們必須使用同樣的超時間隔 返回值 如果函數成功返回非零值如果失敗返回0詳細消息可以調用GetLastError函數獲得
Sample Code
1 // 服務端.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <iostream> 7 using namespace std; 8 9 int main() 10 { 11 HANDLE hPipe = NULL; 12 HANDLE hEvent = NULL; 13 DWORD dwReadLen = 0; 14 DWORD dwWriteLen = 0; 15 OVERLAPPED ovlap; 16 char senbuf[] = "This is server!"; 17 char rebuf[100]; 18 19 //1. 創建命名管道 20 hPipe = CreateNamedPipe(L"\\\\.\\pipe\\Communication", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, 1, 1024, 1024, 0, NULL); 21 if (INVALID_HANDLE_VALUE == hPipe) 22 { 23 cout << "創建命名管道失敗!" << endl; 24 hPipe = NULL; 25 system("pause"); 26 return -1; 27 } 28 29 hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 30 if (NULL == hEvent) 31 { 32 cout << "創建事件對象失敗!" << endl; 33 CloseHandle(hPipe); 34 hPipe = NULL; 35 system("pause"); 36 return -1; 37 } 38 39 ZeroMemory(&ovlap, sizeof(OVERLAPPED)); 40 ovlap.hEvent = hEvent; 41 42 //2. 創建管道連接 43 if (!ConnectNamedPipe(hPipe, &ovlap)) 44 { 45 if (ERROR_IO_PENDING != GetLastError()) 46 { 47 cout << "等待客戶端連接失敗!" << endl; 48 CloseHandle(hPipe); 49 CloseHandle(hEvent); 50 hPipe = NULL; 51 system("pause"); 52 return -1; 53 } 54 } 55 56 //3. 等待客戶端連接 57 if ( WAIT_FAILED == WaitForSingleObject(hEvent, INFINITE)) 58 { 59 cout << "等待對象失敗!" << endl; 60 CloseHandle(hPipe); 61 CloseHandle(hEvent); 62 hPipe = NULL; 63 system("pause"); 64 return -1; 65 } 66 CloseHandle(hEvent); 67 68 //4. 讀寫管道數據 69 //4.1 讀取數據 70 if (!ReadFile(hPipe, rebuf, 100, &dwReadLen, NULL)) 71 { 72 cout << "讀取數據失敗!" << endl; 73 system("pause"); 74 return -1; 75 } 76 cout << rebuf << endl; 77 78 //4.2 寫入數據 79 if (!WriteFile(hPipe, senbuf, (DWORD)strlen(senbuf) + 1, &dwWriteLen, NULL)) 80 { 81 cout << "寫入數據失敗!" << endl; 82 system("pause"); 83 return -1; 84 } 85 86 system("pause"); 87 return 0; 88 }
1 // 客戶端.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <iostream> 7 using namespace std; 8 9 int main() 10 { 11 HANDLE hPipe = NULL; 12 HANDLE hEvent = NULL; 13 DWORD dwReadLen = 0; 14 DWORD dwWriteLen = 0; 15 char senbuf[] = "This is client!"; 16 char rebuf[100]; 17 18 //1. 連接命名管道 19 if (!WaitNamedPipe(L"\\\\.\\pipe\\Communication", NMPWAIT_WAIT_FOREVER)) 20 { 21 cout << "當前沒有可利用的命名管道實例!" << endl; 22 system("pause"); 23 return -1; 24 } 25 26 //2. 打開命名管道 27 hPipe = CreateFile(L"\\\\.\\pipe\\Communication", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 28 if (INVALID_HANDLE_VALUE == hPipe) 29 { 30 cout << "打開命名管道失敗!" << endl; 31 hPipe = NULL; 32 system("pause"); 33 return -1; 34 } 35 36 //3. 讀寫管道數據 37 //3.1 寫入數據 38 if (!WriteFile(hPipe, senbuf, strlen(senbuf) + 1, &dwWriteLen, NULL)) 39 { 40 cout << "寫入數據失敗!" << endl; 41 system("pause"); 42 return -1; 43 } 44 45 //3.2 讀取數據 46 if (!ReadFile(hPipe, rebuf, 100, &dwReadLen, NULL)) 47 { 48 cout << "讀取數據失敗!" << endl; 49 system("pause"); 50 return -1; 51 } 52 cout << rebuf << endl; 53 54 system("pause"); 55 return 0; 56 }
2.5 郵件槽
通信流程:
服務器端: 創建郵槽對象 -> 讀取數據 -> 關閉郵槽對象
客戶端:打開郵槽對象 -> 寫入數據 -> 關閉郵槽對象
注意:
(1)郵槽是基於廣播通信體系設計出來的,它采用無連接的不可靠的數據傳輸。
(2)郵槽可以實現一對多的單向通信,我們可以利用這個特點編寫一個網絡會議通知系統,而且實現這一的系統所需要編寫的代碼非常少.如果讀者是項目經理,就可以給你手下每一位員工的機器上安裝上這個系統中的郵槽服務器端程序,在你自己的機器上安裝油槽的客戶端程序,這樣,當你想通知員工開會,就可以通過自己安裝的郵槽客戶端程序.將開會這個消息發送出去,因為機器上都安裝了郵槽服務器端的程序,所以他們都能同時收到你發出的會議通知.采用郵槽實現這一的程序非常簡單的,如果采用Sockets來實現這一的通信,代碼會比較復雜。
(3)郵槽是一種單向通信機制,創建郵槽的服務器進程只能讀取數據,打開郵槽的客戶機進程只能寫入數據。
(4)為保證郵槽在各種Windows平台下都能夠正常工作,我們傳輸消息的時候,應將消息的長度限制在424字節以下。
CreateMailslot函數詳解
函數原型:
HANDLE CreateMailslot( LPCTSTR lpName, // mailslot name DWORD nMaxMessageSize, // maximum message size DWORD lReadTimeout, // read time-out interval LPSECURITY_ATTRIBUTES lpSecurityAttributes // inheritance option );
參數說明:
lpName
指向一個空終止字符串的指針,該字符串指定了油槽的名稱,該名稱的格式必須是:"\\.\mailslot\[path]name ",其中前兩個反斜杠之后的字符表示服務器所在機器的名稱,圓點表示是主機;接着是硬編碼的字符串:"mailslot",這個字符串不能改變,但大小寫無所謂;最后是油槽的名稱([path]name)由程序員起名
nMaxMessageSize
用來指定可以被寫入到油槽的單一消息的最大尺寸,為了可以發送任意大小的消息,卡伊將該參數設置為0
lReadTimeout
指定讀寫操作的超時間間隔,以ms為單位,讀取操作在超時之前可以等待一個消息被寫入到這個油槽之中.
如果這個值設置為0,那么若沒有消息可用,該函數立即返回;
如果這個值設置為MAILSOT_WAIT_FOREVER,則函數一直等待,直到有消息可用
lpSecurityAttributes
指向一個SECURITY_ATTRIBUTES結構的指針,可以簡單地給這個參數傳遞NULL值,讓系統為所創建的油槽賦予默認的安全描述符
Sample Code
1 // 服務端.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include<windows.h> 6 #include<iostream> 7 using namespace std; 8 9 int main() 10 { 11 HANDLE hMailslot = INVALID_HANDLE_VALUE; 12 char buf[100] = { '\0' }; 13 DWORD dwReadLen = 0; 14 15 //1. 創建郵槽對象 16 hMailslot = CreateMailslot(L"\\\\.\\mailslot\\Communication", 0, MAILSLOT_WAIT_FOREVER, NULL); 17 if (INVALID_HANDLE_VALUE == hMailslot) 18 { 19 cout << "創建郵槽失敗!" << endl; 20 system("pause"); 21 return -1; 22 } 23 24 //2. 讀取數據 25 if (!ReadFile(hMailslot, buf, 100, &dwReadLen, NULL)) 26 { 27 cout << "讀取數據失敗!" << endl; 28 CloseHandle(hMailslot); 29 system("pause"); 30 return -1; 31 } 32 cout << buf << endl; 33 34 //3. 關閉郵槽對象 35 CloseHandle(hMailslot); 36 37 system("pause"); 38 return 0; 39 }
1 // 客戶端.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include<windows.h> 6 #include<iostream> 7 using namespace std; 8 9 int main() 10 { 11 HANDLE hMailslot = INVALID_HANDLE_VALUE; 12 char buf[100] = "This is a test message for Mailslot!"; 13 DWORD dwWriteLen = 0; 14 15 //1. 打開郵槽對象 16 hMailslot = CreateFile(L"\\\\.\\mailslot\\Communication", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 17 if (INVALID_HANDLE_VALUE == hMailslot) 18 { 19 cout << "打開郵槽失敗!" << endl; 20 system("pause"); 21 return -1; 22 } 23 24 //2. 向郵槽寫入數據 25 if (!WriteFile(hMailslot, buf, strlen(buf) + 1, &dwWriteLen, NULL)) 26 { 27 cout << "寫入數據失敗!" << endl; 28 CloseHandle(hMailslot); 29 system("pause"); 30 return -1; 31 } 32 33 //3. 關閉郵槽對象 34 CloseHandle(hMailslot); 35 36 system("pause"); 37 return 0; 38 }
2.6 剪貼板
剪貼板(Clipped Board)實質是Win32 API中一組用來傳輸數據的函數和消息,為Windows應用程序之間進行數據共享提供了一個中介,Windows已建立的剪切(復制)-粘貼的機制為不同應用程序之間共享不同格式數據提供了一條捷徑。當用戶在應用程序中執行剪切或復制操作時,應用程序把選取的數據用一種或多種格式放在剪貼板上。然后任何其它應用程序都可以從剪貼板上拾取數據,從給定格式中選擇適合自己的格式。
剪貼板是一個非常松散的交換媒介,可以支持任何數據格式,每一格式由一無符號整數標識,對標准(預定義)剪貼板格式,該值是Win32 API定義的常量;對非標准格式可以使用Register Clipboard Format函數注冊為新的剪貼板格式。利用剪貼板進行交換的數據只需在數據格式上一致或都可以轉化為某種格式就行。但剪貼板只能在基於Windows的程序中使用,不能在網絡上使用。
剪貼板(ClipBoard)是內存中的一塊區域,是Windows內置的一個非常有用的工具,通過小小的剪貼板,架起了一座彩橋,使得在各種應用程序之間,傳遞和共享信息成為可能。然而美中不足的是,剪貼板只能保留一份數據,每當新的數據傳入,舊的便會被覆蓋。
通信流程:
發送數據:
(1)打開剪貼板
(2)清空並占據剪貼板
(3)向剪貼板放入數據:
①獲得一塊堆內存GlobalAlloc
②鎖定堆內存GlobalLock並獲得堆內存首地址
③向剪貼板放入數據SetClipboardData
④釋放堆內存GlobalUnlock
(4)關閉剪貼板
接受數據:
(1)打開剪貼板
(2)檢查剪貼板中的數據格式
(3)從剪貼板中獲取數據:
①接受數據GetClipboardData
②用GlobalLock獲得地址並鎖定內存
③用GlobalUnlock解除鎖定內存
(4)關閉剪貼板
相關函數
顯示相關函數 [打開/關閉剪貼板][清空剪貼板][向剪貼板寫入數據][從剪貼板讀取數據][判斷剪貼板數據格式] 打開/關閉剪貼板 函數原型 //打開剪貼板 BOOL OpenClipboard(); //關閉剪貼板 BOOL CloseClipboard(); EmptyClipboard 清空剪貼板 函數原型 BOOL EmptyClipboard(); 說明 只有調用了EmptyClipboard函數后,打開剪貼板的當前窗口才擁有剪貼板.EmptyClipboard函數將清空剪貼板,並釋放剪貼板中的句柄,然后剪貼板的所有權分配給當前窗口. SetClipboardData 向剪貼板寫入數據 函數原型 HANDLE SetClipboardData(UINT uFormat,HANDLE hMem); 參數說明 uFormat 指定剪貼板的格式,這個格式可以是以注冊的格式,或者是任一種標准的形式的剪貼板詳細參見MSDN hMem 具有指定格式的句柄.該參數可以為NULL,指示調用窗口直到有對剪貼板數據的請求時候才提供指定的剪貼板格式的數據.如果窗口采用延時提交技術,則該窗口必須處理WM_RENDERFORMAT和WM_RENDERALLFORMATS消息 返回值 如果函數成功返回的是數據句柄 如果函數失敗返回的是NULL,詳細消息可以調用GetLastError函數獲得 說明 當前調用的SetClipboardData函數的窗口必須是剪貼板的擁有着,而且在這個之前,該程序已經調用了OpenClipboard函數打開剪貼板 當一個提供的進程創建了剪貼板數據之后,知道其他進程獲取剪貼板數據前,這些數據都是要占據內存空間的,如果在剪貼板上放置的數據過大,就會浪費內存空間,降低資源利用率.為了避免這種浪費,就可以采用延遲提交技術,也就是有數據提供進程先提供一個指定格式的空剪貼板數據塊,即把SetClipboardData函數的hMem參數設置為NULL.當需要獲取數據的進程想要從剪貼板上得到數據時,操作系統會向數據提供進程發送WM_RENDERFORMAT消息,而數據提供進程可以響應這個消息,並在此消息的響應函數中,再一次調用SetClipboardData函數,將實際的數據放到剪貼板上,當再次調用SetClipboardData函數時就不需要調用OpenClipboard函數,也不需要調用EmptyClipboard函數.也就是說為了提高資源利用率,避免浪費內存空間,可以采用延遲提交技術.第一次調用SetClipboard函數時,將其hMem參數設置為NULL,在剪貼板上以指定的剪貼板放置一個空剪貼板數據塊 GetClipboardData從剪貼板讀取數據 函數原型 HANDLE GetClipboardData( UINT uFormat ); 參數說明 uFormat 指定返回數據的句柄格式這個格式可以是以注冊的格式,或者是任一種標准的形式的剪貼板詳細參見MSDN 返回值 若函數成功返回的是剪貼板數據內容的指定格式的句柄 若函數失敗返回值是NULL,詳細消息可以調用GetLastError函數獲得 IsClipboardFormatAvailable 判斷剪貼板的數據格式 函數原型 BOOL IsClipboardFormatAvailable(UINT format); 參數說明 uFormat 判斷剪貼板里的數據的句柄格式這個格式可以是以注冊的格式,或者是任一種標准的形式的剪貼板詳細參見MSDN 返回值 若剪貼板中的數據格式句柄為uFormat格式返回非零值 若剪貼板中的數據格式句柄不為uFormat格式返回零
Sample Code
1 // 發送數據.cpp : Defines the entry point for the console application. 2 // 發送數據:即向剪貼板中寫入數據, 復制(Ctrl + C)動作 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <iostream> 7 using namespace std; 8 9 int main() 10 { 11 const char *pStrData = "This is a test string for Clipboard!"; 12 13 //1. 打開剪貼板: 參數指定為 NULL,表明為當前進程打開剪貼板 14 if (OpenClipboard(NULL)) 15 { 16 char *pDataBuf = NULL; 17 18 //2. 清空剪貼板 19 EmptyClipboard(); 20 21 //3. 向剪貼板中放入數據 22 //3.1 獲得一塊堆內存 23 HGLOBAL hGlobalClip = GlobalAlloc(GHND, strlen(pStrData) + 1); 24 25 //3.2 鎖定堆內存並獲得堆內存首地址 26 pDataBuf = (char*)GlobalLock(hGlobalClip); 27 strcpy_s(pDataBuf, strlen(pStrData) + 1, pStrData); 28 29 //3.3 釋放內存 30 GlobalUnlock(hGlobalClip); 31 32 //3.4 向剪貼板放入數據 33 SetClipboardData(CF_TEXT, hGlobalClip); 34 35 //4. 關閉剪貼板 36 CloseClipboard(); 37 } 38 39 system("pause"); 40 return 0; 41 }
1 // 接收數據.cpp : Defines the entry point for the console application. 2 // 接收數據:即從剪貼板中讀取數據, 黏貼(Ctrl + V)動作 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <iostream> 7 using namespace std; 8 9 int main() 10 { 11 //1. 打開剪貼板: 參數指定為 NULL,表明為當前進程打開剪貼板 12 if (OpenClipboard(NULL)) 13 { 14 //2. 判斷剪貼板中的數據格式是否為 CF_TEXT 15 if (IsClipboardFormatAvailable(CF_TEXT)) 16 { 17 //3. 從剪貼板中獲取數據 18 //3.1 從剪貼板中獲取格式為 CF_TEXT 的數據 19 HGLOBAL hGlobalClip = GetClipboardData(CF_TEXT); 20 21 //3.2 用GlobalLock獲得地址並鎖定內存 22 char *pDataBuf = NULL; 23 pDataBuf = (char *)GlobalLock(hGlobalClip); 24 25 //3.3 用GlobalUnlock解除鎖定內存 26 GlobalUnlock(hGlobalClip); 27 28 cout << "從剪貼板中獲取到數據是: " << pDataBuf << endl; 29 } 30 31 //4. 關閉剪貼板 32 CloseClipboard(); 33 } 34 35 system("pause"); 36 return 0; 37 }
2.7 動態連接庫
Win32動態連接庫(DLL)中的全局數據可以被調用DLL的所有進程共享,這就又給進程間通信開辟了一條新的途徑,當然訪問時要注意同步問題。
雖然可以通過DLL進行進程間數據共享,但從數據安全的角度考慮,我們並不提倡這種方法,使用帶有訪問權限控制的共享內存的方法更好一些。
2.8 遠程過程調用
Win32 API提供的遠程過程調用(RPC)使應用程序可以使用遠程調用函數,這使在網絡上用RPC進行進程通信就像函數調用那樣簡單。RPC既可以在單機不同進程間使用也可以在網絡中使用。
由於Win32 API提供的RPC服從OSF-DCE(Open Software Foundation Distributed Computing Environment)標准。所以通過Win32 API編寫的RPC應用程序能與其它操作系統上支持DEC的RPC應用程序通信。使用RPC開發者可以建立高性能、緊密耦合的分布式應用程序。
2.9 Sockets
Windows Sockets規范是以U.C.Berkeley大學BSD UNIX中流行的Socket接口為范例定義的一套Windows下的網絡編程接口。除了Berkeley Socket原有的庫函數以外,還擴展了一組針對Windows的函數,使程序員可以充分利用Windows的消息機制進行編程。
現在通過Sockets實現進程通信的網絡應用越來越多,這主要的原因是Sockets的跨平台性要比其它IPC機制好得多,另外WinSock 2.0不僅支持TCP/IP協議,而且還支持其它協議(如IPX)。Sockets的唯一缺點是它支持的是底層通信操作,這使得在單機的進程間進行簡單數據傳遞不太方便,這時使用下面將介紹的WM_COPYDATA消息將更合適些。
2.10 WM_COPYDATA消息
WM_COPYDATA是一種非常強大卻鮮為人知的消息。當一個應用向另一個應用傳送數據時,發送方只需使用調用SendMessage函數,參數是目的窗口的句柄、傳遞數據的起始地址、WM_COPYDATA消息。接收方只需像處理其它消息那樣處理WM_COPY DATA消息,這樣收發雙方就實現了數據共享。
WM_COPYDATA是一種非常簡單的方法,它在底層實際上是通過文件映射來實現的。它的缺點是靈活性不高,並且它只能用於Windows平台的單機環境下。
1 HWND hReceiveWnd = NULL; 2 hReceiveWnd = ::FindWindow(NULL, L"ReceiveData"); 3 if (NULL == hReceiveWnd) 4 { 5 MessageBox(L"沒有找到接受窗口!", L"ERROR", MB_ICONERROR); 6 return; 7 } 8 9 CString strSendData = _T("This is test for WM_COPYDATA between processes!"); 10 COPYDATASTRUCT copyData = { 0 }; 11 copyData.lpData = strSendData.GetBuffer(); 12 copyData.cbData = (strSendData.GetLength() + 1) * sizeof(TCHAR); 13 14 ::SendMessage(hReceiveWnd, WM_COPYDATA, (WPARAM)GetSafeHwnd(), (LPARAM)©Data); 15 16 strSendData.ReleaseBuffer();
1 switch (message) 2 { 3 case WM_COPYDATA: 4 { 5 COPYDATASTRUCT *pCDS = (COPYDATASTRUCT*)lParam; 6 CString strRecvData = _T(""); 7 strRecvData.Format(_T("%s"), pCDS->lpData); 8 9 MessageBox(strRecvData, L"接收到的數據", MB_OK); 10 } 11 break; 12 default: 13 break; 14 }
