1 进程与进程通讯ios
进程是装入内存并准备执行的程序,每一个进程都有私有的虚拟地址空间,由代码、数据以及它可利用的系统资源(如文件、管道等)组成。多进程/多线程是Windows操做系统的一个基本特征。Microsoft Win32应用编程接口(Application Programming Interface, API)提供了大量支持应用程序间数据共享和交换的机制,这些机制行使的活动称为进程间通讯(InterProcess Communication, IPC),进程通讯就是指不一样进程间进行数据共享和数据交换。
正由于使用Win32 API进行进程通讯方式有多种,如何选择恰当的通讯方式就成为应用开发中的一个重要问题,下面本文将对Win32中进程通讯的几种方法加以分析和比较。程序员
2 进程通讯方法编程
2.1 文件映射
文件映射(Memory-Mapped Files)能使进程把文件内容看成进程地址区间一块内存那样来对待。所以,进程没必要使用文件I/O操做,只需简单的指针操做就可读取和修改文件的内容。
Win32 API容许多个进程访问同一文件映射对象,各个进程在它本身的地址空间里接收内存的指针。经过使用这些指针,不一样进程就能够读或修改文件的内容,实现了对文件中数据的共享。
应用程序有三种方法来使多个进程共享一个文件映射对象。
(1)继承:第一个进程创建文件映射对象,它的子进程继承该对象的句柄。
(2)命名文件映射:第一个进程在创建文件映射对象时能够给该对象指定一个名字(可与文件名不一样)。第二个进程可经过这个名字打开此文件映射对象。另外,第一个进程也能够经过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。
(3)句柄复制:第一个进程创建文件映射对象,而后经过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
文件映射是在多个进程间共享数据的很是有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。windows
#include <windows.h> #include <iostream> using namespace std; int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HANDLE hFile = CreateFile("D:\\Demo.txt", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwHigh = 0; DWORD dwLow = 0; dwLow = GetFileSize(hFile, &dwHigh); dwLow = ((dwLow + 4095) / 4096) * 4096; if (hFile == INVALID_HANDLE_VALUE) { return -1; } HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, dwHigh, dwLow, NULL); if (hMapping == NULL) { CloseHandle(hFile); } char* szBuffer = NULL; szBuffer = (char*)MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (szBuffer != NULL) { cout << szBuffer << endl; } *(szBuffer + 1) = 'w'; UnmapViewOfFile(szBuffer); CloseHandle(hMapping); CloseHandle(hFile); system("pause"); return nRetCode; }
2.2 共享内存
Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊状况。进程在建立文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操做系统页面文件访问内存,其它进程打开该文件映射对象就能够访问该内存块。因为共享内存是用文件映射实现的,因此它也有较好的安全性,也只能运行于同一计算机上的进程之间。安全
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <iostream> using namespace std; int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; char szBuffer[] = "ONDragon"; HANDLE hMapping = CreateFileMapping(NULL, NULL, PAGE_READWRITE, 0, 4096, "ShareMemory"); LPVOID lpBase = MapViewOfFile(hMapping, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0); strcpy((char*)lpBase, szBuffer); Sleep(20000); UnmapViewOfFile(lpBase); CloseHandle(hMapping); return nRetCode; }
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <iostream> int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HANDLE hMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, NULL, "ShareMemory"); if (hMapping) { wprintf(L"%s\r\n", "Success"); LPVOID lpBase = MapViewOfFile(hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); char szBuffer[20] = { 0 }; strcpy(szBuffer, (char*)lpBase); printf("%s", szBuffer); UnmapViewOfFile(lpBase); CloseHandle(hMapping); } else { wprintf(L"%s", "OpenMapping Error"); } return nRetCode; }
2.3 匿名管道
管道(Pipe)是一种具备两个端点的通讯通道:有一端句柄的进程能够和有另外一端句柄的进程通讯。管道能够是单向-一端是只读的,另外一端点是只写的;也能够是双向的一管道的两端点既可读也可写。
匿名管道(Anonymous Pipe)是 在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。一般由父进程建立管道,而后由要通讯的子进程继承通道的读端点句柄或写 端点句柄,而后实现通讯。父进程还能够创建两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可使用管道直接通讯,不须要经过父进程。
匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
2.4 命名管道
命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通讯的单向或双向管道。不一样于匿名管道的是命名管道能够在不相关的进程之间和不一样计算机之间使用,服务器创建命名管道时给它指定一个名字,任何进程均可以经过该名字打开管道的另外一端,根据给定的权限和服务器进程通讯。
命名管道提供了相对简单的编程接口,使经过网络传输数据并不比同一计算机上两进程之间通讯更困难,不过若是要同时和多个进程通讯它就力不从心了。服务器
#include <windows.h> #include <iostream> using namespace std; #define READ_PIPE "\\\\.\\pipe\\ReadPipe" #define WRITE_PIPE "\\\\.\\pipe\\WritePipe" // 管道命名 #define Log(str) {{printf("%s\n",str);}} typedef struct _USER_CONTEXT_ { HANDLE hPipe; HANDLE hEvent; }USER_CONTEXT, *PUSER_CONTEXT; USER_CONTEXT Context[2] = { 0 }; HANDLE hThread[2] = { 0 }; BOOL WritePipe(); BOOL ReadPipe(); BOOL bOk = FALSE; DWORD WINAPI WritePipeThread(LPVOID LPParam); DWORD WINAPI ReadPipeThread(LPVOID LPParam); int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HANDLE hPipe = NULL; if (WritePipe() == FALSE) { return -1; } if (ReadPipe() == FALSE) { return -1; } int iIndex = 0; while (TRUE) { if (bOk == TRUE) { SetEvent(Context[0].hEvent); SetEvent(Context[1].hEvent); Sleep(1); } iIndex = WaitForMultipleObjects(2, hThread, TRUE, 5000); if (iIndex == WAIT_TIMEOUT) { continue; } else { break; } } int i = 0; for (i = 0; i < 2; i++) { CloseHandle(Context[i].hEvent); CloseHandle(Context[i].hPipe); } CloseHandle(hThread[0]); CloseHandle(hThread[1]); cout << "Exit" << endl; return nRetCode; } BOOL WritePipe() { HANDLE hWritePipe = NULL; //Create A Named Pipe hWritePipe = CreateNamedPipe( WRITE_PIPE, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, MAX_PATH, MAX_PATH, 0, NULL); if (hWritePipe == INVALID_HANDLE_VALUE) { return FALSE; } HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); Context[0].hEvent = hEvent; Context[0].hPipe = hWritePipe; hThread[0] = CreateThread(NULL, 0, WritePipeThread, NULL, 0, NULL); return TRUE; } BOOL ReadPipe() { HANDLE hReadPipe = NULL; hReadPipe = CreateNamedPipe( READ_PIPE, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, MAX_PATH, MAX_PATH, 0, NULL); if (hReadPipe == INVALID_HANDLE_VALUE) { return FALSE; } HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); Context[1].hEvent = hEvent; Context[1].hPipe = hReadPipe; hThread[1] = CreateThread(NULL, 0, ReadPipeThread, NULL, 0, NULL); return TRUE; } DWORD WINAPI ReadPipeThread(LPVOID LPParam) { HANDLE hEvent = Context[1].hEvent; HANDLE hReadPipe = Context[1].hPipe; DWORD dwReturn = 0; char szBuffer[MAX_PATH] = { 0 }; int iIndex = 0; while (TRUE) { iIndex = WaitForSingleObject(hEvent, 30); iIndex = iIndex - WAIT_OBJECT_0; if (iIndex == WAIT_FAILED || iIndex == 0) { break; } if (ReadFile(hReadPipe, szBuffer, MAX_PATH, &dwReturn, NULL)) { szBuffer[dwReturn] = '\0'; cout << szBuffer << endl; } else { if (GetLastError() == ERROR_INVALID_HANDLE) { break; } } } return 0; } //Write Pipe DWORD WINAPI WritePipeThread(LPVOID LPParam) { HANDLE hEvent = Context[0].hEvent; HANDLE hWritePipe = Context[0].hPipe; DWORD dwReturn = 0; char szBuffer[MAX_PATH] = { 0 }; int iIndex = 0; while (TRUE) { iIndex = WaitForSingleObject(hEvent, 30); iIndex = iIndex - WAIT_OBJECT_0; if (iIndex == WAIT_FAILED || iIndex == 0) { break; } cin >> szBuffer; if (WriteFile(hWritePipe, szBuffer, strlen(szBuffer), &dwReturn, NULL)) { Log("WritePipe Successful"); } else { if (GetLastError() == ERROR_INVALID_HANDLE) { break; } } } return 0; }
#include <windows.h> #include <iostream> using namespace std; //pipe name #define WRITE_PIPE "\\\\.\\pipe\\ReadPipe" #define READ_PIPE "\\\\.\\pipe\\WritePipe" //thread array HANDLE hThread[2] = { 0 }; //pipe function DWORD WINAPI ReadPipeThread(LPARAM LPParam); DWORD WINAPI WritePipeThread(LPARAM LPParam); int main(int argc, TCHAR* argv[], TCHAR* envp[]) { HANDLE hReadPipe = NULL; HANDLE hWritePipe = NULL; //Create Thread About Pipe hThread[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadPipeThread, NULL, 0, NULL); hThread[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WritePipeThread, NULL, 0, NULL); //Wait WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CloseHandle(hReadPipe); CloseHandle(hWritePipe); CloseHandle(hThread[0]); CloseHandle(hThread[1]); cout << "Exit" << endl; return -1; } DWORD WINAPI WritePipeThread(LPARAM LPParam) { HANDLE hWritePipe = NULL; char szBuffer[MAX_PATH] = { 0 }; DWORD dwReturn = 0; while (TRUE) { //CreatePipe hWritePipe = CreateFile(WRITE_PIPE, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hWritePipe == INVALID_HANDLE_VALUE) { continue; } break; } while (TRUE) { cin >> szBuffer; if (WriteFile(hWritePipe, szBuffer, MAX_PATH, &dwReturn, NULL)) { } else { if (GetLastError() == ERROR_NO_DATA) { cout << "Write Failed" << endl; break; } } } return 0; } DWORD WINAPI ReadPipeThread(LPARAM LPParam) { HANDLE hReadPipe = NULL; char szBuffer[MAX_PATH] = { 0 }; DWORD dwReturn = 0; while (TRUE) { //CreatePipe hReadPipe = CreateFile(READ_PIPE, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hReadPipe == INVALID_HANDLE_VALUE) { continue; } break; } while (TRUE) { if (ReadFile(hReadPipe, szBuffer, MAX_PATH, &dwReturn, NULL)) { szBuffer[dwReturn] = '\0'; cout << szBuffer; } else { cout << "Read Failed" << endl; break; } } return 0; }
2.5 邮件槽
邮件槽(Mailslots)提 供进程间单向通讯能力,任何进程都能创建邮件槽成为邮件槽服务器。其它进程,称为邮件槽客户,能够经过邮件槽的名字给邮件槽服务器进程发送消息。进来的消 息一直放在邮件槽中,直到服务器进程读取它为止。一个进程既能够是邮件槽服务器也能够是邮件槽客户,所以可创建多个邮件槽实现进程间的双向通讯。
经过邮件槽能够给本地计算机上的邮件槽、其它计算机上的邮件槽或指定网络区域中全部计算机上有一样名字的邮件槽发送消息。广播通讯的消息长度不能超过400字节,非广播消息的长度则受邮件槽服务器指定的最大消息长度的限制。
邮件槽与命名管道类似,不过它传输数据是经过不可靠的数据报(如TCP/IP协议中的UDP包)完成的,一旦网络发生错误则没法保证消息正确地接收,而命名管道传输数据则是创建在可靠链接基础上的。不过邮件槽有简化的编程接口和给指定网络区域内的全部计算机广播消息的能力,因此邮件槽不失为应用程序发送和接收消息的另外一种选择。网络
#include <windows.h> #include <iostream> using namespace std; #define MAIL_SLOT_NAME "\\\\.\\mailslot\\Name" HANDLE hReadMailSlot = INVALID_HANDLE_VALUE; DWORD WINAPI ReadMail(); int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HANDLE hReadThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadMail, NULL, 0, NULL); Sleep(INFINITE); if (hReadMailSlot != INVALID_HANDLE_VALUE) { CloseHandle(hReadMailSlot); } Sleep(10); return nRetCode; } DWORD WINAPI ReadMail() { hReadMailSlot = CreateMailslot(MAIL_SLOT_NAME, 0, 0, NULL); if (hReadMailSlot == INVALID_HANDLE_VALUE) { return -1; } //查看油槽的信息 DWORD cbMessage = 0; DWORD cMessage = 0; BOOL bOk = FALSE; char* szBuffer = NULL; DWORD dwReturn = 0; while (TRUE) { bOk = GetMailslotInfo(hReadMailSlot, NULL, &cbMessage, &cMessage, NULL); if (bOk == FALSE) { break; } if (cMessage == 0) { continue; } else { if (szBuffer != NULL) { free(szBuffer); szBuffer = NULL; } szBuffer = (char*)malloc(sizeof(char)*cbMessage + 1); if (ReadFile(hReadMailSlot, szBuffer, cbMessage, &dwReturn, NULL) == TRUE) { szBuffer[dwReturn] = '\0'; if (strcmp(szBuffer, "Exit") == 0) { break; } cout << szBuffer << endl; } } } cout << "ReadThread Exit" << endl; }
#include <windows.h> #include <iostream> using namespace std; #define MAIL_SLOT_NAME "\\\\.\\mailslot\\Name" int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HANDLE hWriteMailSlot = NULL; while (TRUE) { hWriteMailSlot = CreateFile(MAIL_SLOT_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hWriteMailSlot == INVALID_HANDLE_VALUE) { continue; } else { break; } } DWORD dwReturn = 0; char szBuffer[1024] = { 0 }; while (TRUE) { cin >> szBuffer; if (strcmp(szBuffer, "Exit") == 0) { break; } WriteFile(hWriteMailSlot, szBuffer, strlen(szBuffer), &dwReturn, NULL); } WriteFile(hWriteMailSlot, szBuffer, strlen(szBuffer), &dwReturn, NULL); CloseHandle(hWriteMailSlot); return nRetCode; }
2.6 剪贴板
剪贴板(Clipped Board)实质是Win32 API中一组用来传输数据的函数和消息,为Windows应用程序之间进行数据共享提供了一个中介,Windows已创建的剪切(复制)-粘贴的机制为不一样应用程序之间共享不一样格式数据提供了一条捷径。当用户在应用程序中执行剪切或复制操做时,应用程序把选取的数据用一种或多种格式放在剪贴板上。而后任何其它应用程序均可以从剪贴板上拾取数据,从给定格式中选择适合本身的格式。
剪贴板是一个很是松散的交换媒介,能够支持任何数据格式,每一格式由一无符号整数标识,对标准(预约义)剪贴板格式,该值是Win32 API定义的常量;对非标准格式可使用Register Clipboard Format函数注册为新的剪贴板格式。利用剪贴板进行交换的数据只需在数据格式上一致或均可以转化为某种格式就行。但剪贴板只能在基于Windows的程序中使用,不能在网络上使用。
2.7 动态数据交换
动态数据交换(DDE)是使用共享内存在应用程序之间进行数据交换的一种进程间通讯形式。应用程序可使用DDE进行一次性数据传输,也能够当出现新数据时,经过发送更新值在应用程序间动态交换数据。
DDE和剪贴板同样既支持标准数据格式(如文本、位图等),又能够支持本身定义的数据格式。但它们的数据传输机制却不一样,一个明显区别是剪贴板操做几乎老是用做对用户指定操做的一次性应答-如从菜单中选择Paste命令。尽管DDE也能够由用户启动,但它继续发挥做用通常没必要用户进一步干预。DDE有三种数据交换方式:
(1) 冷链:数据交换是一次性数据传输,与剪贴板相同。
(2) 温链:当数据交换时服务器通知客户,而后客户必须请求新的数据。
(3) 热链:当数据交换时服务器自动给客户发送数据。
DDE交换能够发生在单机或网络中不一样计算机的应用程序之间。开发者还能够定义定制的DDE数据格式进行应用程序之间特别目的IPC,它们有更紧密耦合的通讯要求。大多数基于Windows的应用程序都支持DDE。
2.8 对象链接与嵌入
应用程序利用对象链接与嵌入(OLE)技术管理复合文档(由多种数据格式组成的文档),OLE提供使某应用程序更容易调用其它应用程序进行数据编辑的服务。例如,OLE支持的字处理器能够嵌套电子表格,当用户要编辑电子表格时OLE库可自动启动电子表格编辑器。当用户退出电子表格编辑器时,该表格已在原始字处理器文档中获得更新。在这里电子表格编辑器变成了字处理器的扩展,而若是使用DDE,用户要显式地启动电子表格编辑器。
同DDE技术相同,大多数基于Windows的应用程序都支持OLE技术。
2.9 动态链接库
Win32动态链接库(DLL)中的全局数据能够被调用DLL的全部进程共享,这就又给进程间通讯开辟了一条新的途径,固然访问时要注意同步问题。
虽然能够经过DLL进行进程间数据共享,但从数据安全的角度考虑,咱们并不提倡这种方法,使用带有访问权限控制的共享内存的方法更好一些。
2.10 远程过程调用
Win32 API提供的远程过程调用(RPC)使应用程序可使用远程调用函数,这使在网络上用RPC进行进程通讯就像函数调用那样简单。RPC既能够在单机不一样进程间使用也能够在网络中使用。
因为Win32 API提供的RPC服从OSF-DCE(Open Software Foundation Distributed Computing Environment)标准。因此经过Win32 API编写的RPC应用程序能与其它操做系统上支持DEC的RPC应用程序通讯。使用RPC开发者能够创建高性能、紧密耦合的分布式应用程序。
2.11 NetBios函数
Win32 API提供NetBios函数用于处理低级网络控制,这主要是为IBM NetBios系统编写与Windows的接口。除非那些有特殊低级网络功能要求的应用程序,其它应用程序最好不要使用NetBios函数来进行进程间通讯。
2.12 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.13 WM_COPYDATA消息
WM_COPYDATA是一种很是强大却不为人知的消息。当一个应用向另外一个应用传送数据时,发送方只需使用调用SendMessage函数,参数是目的窗口的句柄、传递数据的起始地址、WM_COPYDATA消息。接收方只需像处理其它消息那样处理WM_COPY DATA消息,这样收发双方就实现了数据共享。
WM_COPYDATA是一种很是简单的方法,它在底层其实是经过文件映射来实现的。它的缺点是灵活性不高,而且它只能用于Windows平台的单机环境下。多线程