在我学windows编程的时候,对进程间如何通信总是感觉很神秘,网络上介绍的方法很多,但是很少有一个系统的介绍,五花八门的说法让人总是一头雾水,在这里,我整理一下各通信方法,梳理了一下这些方法的优缺点,希望能对各位看官起到抛砖引玉的作用。
非标准的进程间通信技术有:Windows消息,内存映射,内存共享等等。
1. Windows消息实现进程间通信。
消息的接受进程和发送进程都要定义相同的消息。但是,如果发送方仅仅是发送消息,那么发送方可以不实现消息映射,不用定义消息响应函数。接受方需要定义映射和相应函数。
自定义消息的实现进程间通信的缺陷是,由于消息的传递参数是个长整形 lparam, 因此,只能传递整形的数据,而不能传递字符串。有个可以的思路是,传递字符串所在的地址,然后另一个程序通过获得发送方的进程句柄,用函数ReadProcessMemory与WriteProcessMemory操作发送方的内存空间来读取内容。
2. 使用MFC定义的WM_COPYDATA消息
该消息其实与普通的自定义消息通信类似,区别是,传送的是一个指向COPYDATASTRUCT 结构的指针,要传递的字符串就保存在这个结构体里。这个结构的第一个变量 dwData可以设置为0即可。
这个消息与 上面的那个思路的不同是,获得了结构体的指针后,接受进程不需要其他的处理就能获取到指针的数据,就好像在同一个进程里通信一样.
另外注意,该消息的接受有时并不能获得所需要的长度,有可能只接收到了一部分。
因此,该方式只适合于传递是少量的数据。
3.使用进程内存读写函数
基本上与方法一得后面猜想部分相同。关键是,要使用GlobalAlloc()或者VirtualAllocEx来分配内存空间存放数据。把数据写入接收方的进程内存,然后接着就发消息告诉他数据的地址。由于是在发送方申请的内存,那么,最好等待sleep一定时间再VirtualFreeEx申请到得内存。
GlobalAlloc()或者VirtualAllocEx可以实现在另一个进程的内存空间来申请内存,这就为什么上面能进行的原因。也就是说,发送方并不在自己的内存空间申请内存,而是在接收方进程内存空间来申请内存。然后写入输入数据。当然,也可以在发送方的进程空间来申请内存,接收方通过跨进程读写的方式来读。这只是处理方式不同而已。
4.使用内存映射文件的方法
内存映射的好处就是,可以像对待一个文件一样来对待一块内存区。文件时可以被不同的进程来读写的。既然那块内存 区像文件一样,那么这块内存区就可以被不同的进程来读写。
5.使用DLL进行通信
Win32 DLL 只能共享代码不能共享数据,不同的进程载入同一个DLL文件,DLL的代码都只载入了一份到内存,这只载入一份代码仅指同一个DLL文件,如果相同的DLL文件在不同的盘符下,也不是同一个DLL,而是同一DLL多个副本。
Win16 DLL 被载入了系统内存,所以调用它的进程都可以访问到它的全局变量,因此可以很容易的实现进程间通信。但是对于win32 DLL , 操作系统会把该DLL映射到每个调用它的进程的地址空间,DLL成为该进程的一部分。
可以用下面的方法来将DLL的数据区设置为共享区。
#pragma data_seg("SHARED") // 定义名为SHARED的共享数据段
char m_strString[256]=TEXT(""); // 共享的数据。特别要注意需要初始化,因为编译器会把未初始化的变量保存在bss数据段。
volatile bool bInCriticalSection=FALSE; // 同步标示
#pragma data_seg()
#pragma comment(linker,"/SECTION:SHARED,RWS") // 将要共享的数据段通知编译器。
CCriticalSection cs; // 临界区,控制同步的
上面控制了同步问题。
6.使用操作系统提供的剪贴板实现通信
使用剪贴板是一中开销较小的进程通信机制。剪贴板机制的原理是,剪贴板是系统预留的一块全局内存,用来暂存进程间进行数据交换的数据。
提供数据的进程需要先创建一个全局内存块,然后将要传送的数据移到或复制到该内存块。 接收进程需要获得此内存块的句柄,完成数据读取。
下面的程序标明怎么在剪贴板写数据
CString strData=m_strClipBoard; // 获得数据.
// 打开系统剪贴板.
if (!OpenClipboard()) return;
// 使用之前,清空系统剪贴板.
EmptyClipboard();
// 分配一内存,大小等于要拷贝的字符串的大小,返回该内存控制句柄.
HGLOBAL hClipboardData;
hClipboardData = GlobalAlloc(GMEM_DDESHARE, strData.GetLength()+1);
// 内存控制句柄加锁,返回值为指向那内存控制句柄所在的特定数据格式的指针.
char * pchData;
pchData = (char*)GlobalLock(hClipboardData);
// 将本地变量的值赋给全局内存.
strcpy(pchData, LPCSTR(strData));
// 给加锁的全局内存控制句柄解锁.
GlobalUnlock(hClipboardData);
// 通过全局内存句柄将要拷贝的数据放到剪贴板上.
SetClipboardData(CF_TEXT,hClipboardData);
// 使用完后关闭剪贴板.
CloseClipboard();
从剪贴板读取数据的代码如下
// 打开系统剪贴板.
if (!OpenClipboard()) return;
// 判断剪贴板上的数据是否是指定的数据格式.
if (IsClipboardFormatAvailable(CF_TEXT)|| IsClipboardFormatAvailable(CF_OEMTEXT))
{
// 从剪贴板上获得数据.
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
// 通过给内存句柄加锁,获得指向指定格式数据的指针.
char *pchData = (char*)GlobalLock(hClipboardData);
// 本地变量获得数据.
m_strClipBoard = pchData;
// 给内存句柄解锁.
GlobalUnlock(hClipboardData);
}
else
{
AfxMessageBox("There is no text (ANSI) data on the Clipboard.");
}
// 使用完后关闭剪贴板.
CloseClipboard();
// 更新数据.
UpdateData(FALSE);
7.DDE (Dynamic Data Exchange)动态数据交换
目前微软已经停止了开发这种技术,仅仅保留了支持。
高级通信技术
前面都是几种基本的进程通信技术,消息管道(Message Pipes), 邮槽(Mail slots), 和套接字(Sockets)则是实际比较常见的方法,这几种高级通信除了可以像上面的方法样实现本地系统进程间的通信,也可以用于远程不同系统间的通信。
8. 消息管道
Message Pipes又分为匿名管道(anonymous Pipes),和 命名管道(Named Pipes), 匿名管道主要用于本地系统上父进程与他启动的子进程间的通信。命名管道高级些,可以再不同的系统上的进程间通信,因为UNIX, LINUX等都支持这项技术,因此,命名管道技术是比较理想的C/S结构通信技术。
命名管道原理是,一个进程把数据放进管道中,另一个知道管道名字的经常来把数据取走。其他不知道管道名字的进程不可能能把数据取走。 因此,管道实际上就是一块进程间的共享内存。
创建管道的进程叫管道服务器,链接管道的进程就是客户机。创建管道的函数是HANDLE CreateNamedPipe(….);
连接管道 CallNamedPipe(); 读入或写入数据后腰关闭它
ConnectNamedPipe(); 服务器准备好一个连接到客户进程的管道,并一直等待知道客户连接上为止。
DisconnectNamedPipe(); 服务器中断与客户的连接
GetNamedPipeHandleState(); 获取一个命名管道的状态信息
GetNamedPipeInfo();获取一个命名管道的信息
PeekNamedPipe(); 从一个管道中复制数据到一个缓冲区
SetNamedPipeHandleState(); 设置一个管道的状态信息以及管道类型
TransactNamedPipe(); 从一个消息管道读消息或写消息
WaitNamedPipe(); 使服务器等待来自客户的实例连接。
管道通信基本流程
(1)连接建立 服务器端通过ConnectNamedPipe函数建立以个命名管道实例,然后通过ConnectNamedPipe()函数来侦听来自客户端得请求。这个函数可以设置等待时间。
客户端只需使用函数WaitNamedPipe()来连接服务器。
(2)通信实现 建立连接后,就可以通过得到管道的文件句柄利用ReadFile()与WriteFile()进行彼此间的通信。
(3)连接终止 客户端调用CloseFile(), 服务器端调用DisconnectNamedPipe()终止连接。且都需要CloseHandle来关闭管道。
9.邮槽通信
10.套接字通信
套接字通信过程可简单的描述为,主要调用5个函数,socket(),bind(), Listen(), connect(), accept(). 服务器端主要调用socket(),bind(), Listen(),accept()。 客户端主要调用socket(),connect()。 双方的数据传送就是通过send() 与recv()完成。
套接字类型主要有5种,SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET, SOCK_RDM.
10.1 Winsock 程序设计流程
(1)程序编译环境
有两套函数进行Winsock程序设计,Socket1.1 与Socket2.0. 可以灵活混用。Socket2.0得功能较为强大。 包含其中一个头文件及其对应的库文件即可。
// Socket2.0
#include
#pragma comment (lib, “ws2_32.lib”);
// Socket1.1
#include
#pragma comment (lib, “wsock32.lib”);
(2)选择机制(异步?非阻塞?)
默认情况下都是创建的阻塞套接字。可以通过select或者WSAAsynSelect()函数将其变为非阻塞的。特别注意,用这个函数改为非阻塞后,不能简单的利用ioctlsocket()将它再变为阻塞模式。也就是说这连个函数改变阻塞模式是由区别的。ioctlsocket()是将异步模式的套接字再改回阻塞模式,但之前要调用WSAAsynSelect()取消所有的异步事件,WSAAsynSelect(s,hWnd,0,0);
(3)启动与终止
启动函数WSAStartup()建立于Winsock DLL 的连接, 终止函数WSAClearup()终止该DLL,这两个函数必须成对使用。
(4)出错处理
Winsock为了与以后的多线程环境相兼容,提供了两个出错处理函数来获取和设置当前线程的最近错误号,即WSAGetLastError()和WSASetLastError();
Windows 进程间通信技术概略
最新推荐文章于 2023-03-01 15:55:54 发布