一种机制,操作系统进程和线程通过它交换数据和消息。IPC 包括本地机制(如 Windows 共享内存)或网络机制(如 Windows 套接字)。
进程间通讯
一、说明进程间通讯的必要性及困难性
二、Socket的方法,对于不同机器上且数据量很的情况会有很大的帮助,但对于同一台机器之间的不同进程之间的通讯就不方便了 (代码量太多)
三、进程间通讯的剪切板方法
a、对于发送端:
CString str;
GetDlgItemText(IDC_EDIT1,str);
HANDLE hGlobal;
if(this->OpenClipboard())//获取剪切板的资源所有权
{
EmptyClipboard();//将剪切板的内容清空
hGlobal=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);//在堆上分配一块用于存放数据的空间,程序返回一个内存句柄
char* pBuf=(char*)GlobalLock(hGlobal);//将内存块句柄转化成一个指针,并将相应的引用计数器加一
strcpy(pBuf,str.GetBuffer(str.GetLength()));//将字符串拷入指定的内存块中
GlobalUnlock(hGlobal);//将引用计数器数字减一
::SetClipboardData(CF_TEXT,hGlobal);//将存放有数据的内存块放入剪切板的资源管理中
::CloseClipboard();//释放剪切板的资源占用权
}
b、对于客户端
if(this->OpenClipboard())//获取剪切板的资源所有权
{
HANDLE hGlobal=::GetClipboardData(CF_TEXT);从剪切板中取出一个内存的句柄
char* pBuf=(char*)GlobalLock(hGlobal);//将内存句柄值转化为一个指针,并将内存块的引用计数器加一
SetDlgItemText(IDC_EDIT2,pBuf);
GlobalUnlock(hGlobal);//将内存块的引用计数器减一
CloseClipboard();//释放剪切板资源的占用权
}
四、内存映射文件方法
1、 服务器端代码:
HANDLE hMapFile;
hMapFile= CreateFileMapping(NULL,NULL,PAGE_READWRITE,0,10,"YuanMap");
if (hMapFile == NULL)
{
AfxMessageBox("CreateFileMapping出错!");
return;
}
LPVOID pFile;
pFile= MapViewOfFile(hMapFile,FILE_MAP_WRITE|FILE_MAP_READ,0,0,0);
if (pFile == NULL)
{
AfxMessageBox("MapViewOfFile出错!");
return;
}
CString str;
GetDlgItemText(IDC_EDIT1,str);
strcpy((char*)pFile,str.GetBuffer(str.GetLength()));
//CloseHandle(hMapFile); //不能加,否则客户端收不到,所以一般会将这个句柄作为一个全局变量
2、 客户机端代码:
HANDLE hMap;
hMap= OpenFileMapping(FILE_MAP_READ|FILE_MAP_WRITE,
TRUE,
"YuanMap");
LPVOID pVoid;
pVoid=::MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);
CString str=(char*)pVoid;
SetDlgItemText(IDC_EDIT1,str);
UnmapViewOfFile(pVoid);
CloseHandle(hMap);
五、进程间通讯的邮槽方法
1、 邮槽采用的是一种广播机制。
2、 邮槽采用的是一种直接基于文件系统开发而成,所以它不依赖于某种具体的网络协议。
3、 邮槽每次传送的消息长度不能长于422字节。
4、 发送端代码如下:(客户端)
HANDLE hslot;
hslot=CreateFile(".//mailslot//myslot",GENERIC_WRITE,
FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,
NULL);
if(!hslot)
{
MessageBox("打开邮槽失败!");
return;
}
char *pBuf="专业的编程语言培训";
DWORD dwWrite;
WriteFile(hslot,pBuf,strlen(pBuf)+1,&dwWrite,NULL);
CloseHandle(hslot);
5、 接收端代码如下:(服务器端)
HANDLE hMail;
hMail=CreateMailslot(".//mailslot//myslot",0,
MAILSLOT_WAIT_FOREVER,NULL);
if(INVALID_HANDLE_VALUE==hMail)
{
MessageBox("创建邮槽失败!");
return;
}
HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
OVERLAPPED ovlap;
ZeroMemory(&ovlap,sizeof(ovlap));
ovlap.hEvent=hEvent;
char buf[200];
DWORD dwRead;
if(FALSE==ReadFile(hMail,buf,200,&dwRead,&ovlap))
{
if(ERROR_IO_PENDING!=GetLastError())
{
MessageBox("读取操作失败!");
CloseHandle(hMail);
return;
}
}
WaitForSingleObject(hEvent,INFINITE);
MessageBox(buf);
ResetEvent(hEvent);
CloseHandle(hMail);
六、进程间通讯的命令管道方法
A、对于发送端代码如下:
HANDLE handle;
handle=CreateNamedPipe(".//pipe//MyPipe",
PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
1,0,0,1000,NULL);//创建一个命名管道连结
ConnectNamedPipe(handle,NULL);//在命名管道实例上监听客户机连结请求
char buf[200]="http://www.it315.org";
DWORD dwWrite;
WriteFile(handle,buf,strlen(buf)+1,&dwWrite,NULL);//往管道里写数据
CloseHandle(handle);//关闭管道
B、对于接收端代码如下:
HANDLE hNamedPipe;
WaitNamedPipe(".//pipe//MyPipe",NMPWAIT_WAIT_FOREVER);//等候一个命名管道实例可供自己使用
hNamedPipe=CreateFile(".//pipe//MyPipe",GENERIC_READ,FILE_SHARE_READ,
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);//建立与命名管道的连结
char buf[200];
DWORD dwRead;
ReadFile(hNamedPipe,buf,200,&dwRead,NULL);//从命名管道中读取数据
MessageBox(buf);
CloseHandle(hNamedPipe);//关闭与命名管道服务器的连结
七、进程间通讯的匿名管道方法
父进程:
A、对于父进程中创建一个管道代码如下:
SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(sa);
sa.bInheritHandle=TRUE;
sa.lpSecurityDescriptor=NULL;
if(FALSE==CreatePipe(&hRead,&hWrite,&sa,0))//创建一个匿名的管道,得到一个用于从管道读取的句柄,一个用于向管道写数据用的句柄
{
MessageBox("Create pipe failed!");
return;
}
STARTUPINFO sui;
ZeroMemory(&sui,sizeof(sui));
sui.cb=sizeof(sui);
sui.dwFlags=STARTF_USESTDHANDLES;
sui.hStdInput=hRead;
sui.hStdOutput=hWrite;
sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);
PROCESS_INFORMATION pi;
CreateProcess("..//PipeCli//Debug//PipeCli.exe",NULL,
NULL,NULL,TRUE,CREATE_DEFAULT_ERROR_MODE,/*0*/
NULL,NULL,&sui,&pi);//创建一个新的子进程,并将准备好的句柄信息传给子进程
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
B、父进程中从管道读取代码如下:
char buf[200];
DWORD dwRead;
ReadFile(hRead,buf,200,&dwRead,NULL);
MessageBox(buf);
C、父进程中往管道写入代码如下:
char buf[200]="专业的编程语言培训";
DWORD dwWrite;
WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL);
子进程:
首先得到用于管道读取与写入用的句柄值(最好是放在视图的初始化更新函数里)
hRead=GetStdHandle(STD_INPUT_HANDLE);
hWrite=GetStdHandle(STD_OUTPUT_HANDLE);
读取部分代码:
char buf[200];
DWORD dwRead;
ReadFile(hRead,buf,200,&dwRead,NULL);
MessageBox(buf);
写入部分代码:
char buf[200]="http://www.it315.org";
DWORD dwWrite;
WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL);
--------------------------------------------(完)
1 进程与进程通信
进程是装入内存并准备执行的程序,每个进程都有私有的虚拟地址空间,由代码、数据以及它可利用的系统资源(如文件、管道等)组成。多进程/多线 程是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机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。
2.2 共享内存
Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映 射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。由于共享内存是用文件映射实现的,所以它也有较好的安全性,也只能 运行于同一计算机上的进程之间。
2.3 匿名管道
管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。
匿名管道(Anonymous Pipe)是在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读 端点句柄或写端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进 程。
匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
2.4 命名管道
命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器 建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
2.5 邮件槽
邮件槽(Mailslots)提供进程间单向通信能力,任何进程都能建立邮件槽成为邮件槽服务器。其它进程,称为邮件槽客户,可以通过邮件槽的名字给 邮件槽服务器进程发送消息。进来的消息一直放在邮件槽中,直到服务器进程读取它为止。一个进程既可以是邮件槽服务器也可以是邮件槽客户,因此可建立多个邮 件槽实现进程间的双向通信。
通过邮件槽可以给本地计算机上的邮件槽、其它计算机上的邮件槽或指定网络区域中所有计算机上有同样名字的邮件槽发送消息。广播通信的消息长度不能超过400字节,非广播消息的长度则受邮件槽服务器指定的最大消息长度的限制。
邮件槽与命名管道相似,不过它传输数据是通过不可靠的数据报(如TCP/IP协议中的UDP包)完成的,一旦网络发生错误则无法保证消息正确地接收, 而命名管道传输数据则是建立在可靠连接基础上的。不过邮件槽有简化的编程接口和给指定网络区域内的所有计算机广播消息的能力,所以邮件槽不失为应用程序发 送和接收消息的另一种选择。
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平台的单机环境下。
3 结束语
Win32 API为应用程序实现进程间通信提供了如此多种选择方案,那么开发者如何进行选择呢?通常在决定使用哪种IPC方法之前应考虑下一些问题,如应用程序是在网络环境下还是在单机环境下工作等。