05-进程通信
进程通信
所谓进程通信
,其本质就是共享内存
一,邮槽
邮槽(Mailslot)是一种简单的 进程间通信(IPC)机制,它允许不同进程之间传递信息。邮槽的设计比较简单,适用于 单向通信 的场景,也就是说,一个进程只能向邮槽写入数据,另一个进程才能从中读取数据。它是 Windows 操作系统提供的一个特性。
邮槽的工作原理:
- 服务端:创建一个邮槽(Mailslot),用于接收客户端的消息。服务端可以是一个持续运行的程序,它会持续等待其他进程向它的邮槽写入数据。
- 客户端:客户端打开已经存在的邮槽,并向邮槽写入消息。客户端只能写入数据,无法读取数据。
特点:
- 单向通信:邮槽只允许单向通信,即客户端只能向邮槽写数据,服务端只能从邮槽读取数据。
- 广播通信:邮槽支持广播通信,一个进程可以向多个接收者发送消息。如果多个客户端监听同一个邮槽,它们会收到相同的消息。
- 简易性:与其他更复杂的 IPC 机制(如共享内存、管道等)相比,邮槽的实现简单,适用于需要快速且简单实现的场景。
- 无连接:邮槽是无连接的通信机制,客户端和服务端不需要事先建立连接,服务端只需要创建一个邮槽,客户端可以随时通过路径打开它。
使用场景:
邮槽适合用于简单的单向消息传递,例如:
- 客户端向服务器发送一个通知或请求。
- 服务端向多个客户端广播某个事件。
它在一些简单的应用程序或旧版 Windows 系统中可能仍然使用,但在现代应用程序中,可能会选择更灵活和高效的通信机制,如 管道 或 网络套接字。
邮槽的创建与使用:
- 创建邮槽:服务端创建一个邮槽,指定它的路径。
- 使用
CreateMailslot
函数来创建邮槽。
- 使用
- 写入数据:客户端向邮槽写入数据。
- 使用
CreateFile
打开邮槽,并使用WriteFile
向其写入数据。
- 使用
- 读取数据:服务端从邮槽中读取数据。
- 使用
ReadFile
从邮槽读取数据。
- 使用
示例:
服务端代码如下:Process1.cpp
1 |
|
客户端代码如下:Process2.cpp
1 |
|
最后成功向邮槽中写入:Hello world!
如图:
等待写入消息:process1.cpp
成功向邮槽中写入内容:如下:
二,管道
管道(Pipe) 是一种常见的进程间通信(IPC)机制,允许不同进程之间通过共享内存区域进行数据交换。管道可以看作是一个“数据通道”,进程可以通过它将数据传递给另一个进程。
管道的类型:
- 匿名管道(Anonymous Pipe):
- 只能在父子进程之间使用,即一个父进程和它的子进程之间传递数据。
- 通常用于在同一台机器上进行进程间的简单通信。
- 数据传输是单向的,但可以通过创建两个管道(一个用于从父进程到子进程,另一个用于反向通信)来实现双向通信。
- 命名管道(Named Pipe):
- 可以在任意进程之间进行通信,不限于父子进程,可以是不同计算机上的进程。
- 它通过一个名字在系统中创建,允许不同的进程通过该名字打开管道进行数据交换。
- 命名管道支持双向通信。
管道的工作原理:
- 写入端(写入数据):一个进程将数据写入管道的写入端。
- 读取端(读取数据):另一个进程从管道的读取端读取数据。
匿名管道:
匿名管道通常在父子进程之间使用,可以在创建管道时指定管道的读端和写端。匿名管道适合于同一台计算机上进程间的简单数据传输。
命名管道:
命名管道在系统中有一个名字,可以被任何进程通过该名字访问,因此它不仅可以用于本地进程之间的通信,还可以用于跨网络进行通信。命名管道可以支持双向通信,即允许数据在两个方向上流动。
管道的特点:
- 单向或双向通信:匿名管道通常是单向的(写入端和读取端),而命名管道可以支持双向通信。
- 进程间通信:管道是为了进程间的通信设计的,可以在父子进程间,或者是跨进程(甚至跨计算机)进行数据交换。
- 阻塞性:默认情况下,管道是阻塞的。如果管道的缓冲区满了,写操作将被阻塞;如果管道为空,读取操作会被阻塞。
管道的创建与使用:
- 创建管道:在 Windows 中,匿名管道使用
CreatePipe
函数创建;命名管道使用CreateNamedPipe
函数创建。 - 读写管道:进程可以通过
ReadFile
和WriteFile
来进行管道的读写操作。
示例:
下面两个进程是不同进程:
process1.cpp:
1 |
|
process2.cpp:
1 |
|
最后运行结果如下:
三,剪切板
进程通信
的第一种方式:剪切板
剪切板(Clipboard)是操作系统提供的一种临时存储区域,用于在不同应用程序之间传输数据。剪切板可以保存文本、图像、文件、格式化数据等信息,允许用户通过复制、剪切和粘贴操作在不同程序或不同进程之间交换数据。
剪切板的工作原理:
- 复制(Copy):用户选择要复制的数据,操作系统将其保存到剪切板。
- 剪切(Cut):用户选择要剪切的数据,操作系统将数据从源位置移至剪切板,并标记源位置为空(如删除)。
- 粘贴(Paste):用户从剪切板粘贴数据到目标位置,操作系统将数据从剪切板传输到目标应用程序或文件。
常见的剪切板数据类型:
- 文本数据:普通文本(如字符串)或富文本(带格式的文本,如加粗、斜体)。
- 图像数据:位图、图形图像(如JPEG、PNG)。
- 文件数据:文件或文件路径。
- 自定义数据格式:特定应用程序定义的数据格式。
使用剪切板进行进程间通信:
通过剪切板,两个进程(应用程序)可以共享数据。在一个进程中将数据放入剪切板,另一个进程可以读取这些数据。
示例:
process1.cpp:
1 |
|
process2.cpp:
1 |
|
我们可以直接运行这段代码,可以看到,剪切板多了如下内容:
我们再写一段代码,来读取剪切板:
process2.cpp:
1 |
|
我们可以看到其内容如下:
四,WM_COPYDATA
进程通信
的第二种方式:利用WM_COPYDATA
WM_COPYDATA
是 Windows 消息机制中的一种特殊消息,用于进程间通信。它通常用于一个进程将数据发送到另一个进程。与其他消息不同,WM_COPYDATA
允许一个进程通过一个结构体传递数据,而无需通过文件或剪贴板等中介。
基本概念:
WM_COPYDATA
消息的发送者和接收者之间通过一个 COPYDATASTRUCT
结构体进行数据交换。这个结构体包含了数据的指针和数据的大小等信息。
结构体 COPYDATASTRUCT
:
1 | typedef struct tagCOPYDATASTRUCT { |
dwData:可以用来传递一些额外的标识信息,通常是一个指针或者整数。
cbData:要发送的数据的大小(字节数)。
lpData:指向要传输的数据的指针。
发送 WM_COPYDATA
消息:
WM_COPYDATA
消息通过 SendMessage
或 PostMessage
发送。发送者通过该消息将数据传递给接收者。
示例:
首先我们创建两个基于对话框
的新项目:如下:
创建完毕以后点击资源视图
,再点击Dialog
,如下:
他就会弹出一个可视化对话框
,然后双击确定按钮:
然后我写入如下代码:
1 | void CwmcopydataDlg::OnBnClickedOk() |
继续打开另外一个项目,然后右键,点击类向导
:
然后将其Caption
设置为123
,如下:
点击消息
,然后输入WM_COPYDATA
:如下
写入如下代码:
1 | BOOL Cwmcopydata2Dlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) |
先运行后者,再运行前者,然后点击前者的确定按钮我们会发现,其弹窗在后者窗口上,成功实现了进程通信。
如下:
左侧是发送端,右侧是接受端,我们可以看到点击发送端确定按钮,就成功发送过去了。
说明:
- 发送端使用
SendMessage
将数据通过WM_COPYDATA
消息发送给接收端。 - 接收端的窗口过程会处理
WM_COPYDATA
消息,提取数据并可以进行后续处理。
优点:
WM_COPYDATA
是一种相对简单且高效的进程间通信方式,特别适用于跨进程的数据交换。- 与管道、套接字等方法相比,它更适合小规模的数据传递。
注意:
- 发送的数据需要遵循约定的格式,以便接收端能够正确解码。
- 数据大小和类型需要通过
COPYDATASTRUCT
明确指定。
五,文件映射
进程通信
的第三种方式:文件映射
文件映射(File Mapping)是Windows操作系统提供的一种机制,允许将磁盘上的文件映射到进程的虚拟内存空间,使得文件内容可以像访问内存一样被进程操作。文件映射的一个重要优势是,它能够在多个进程之间共享文件内容,从而实现进程间通信(IPC),并且可以避免频繁的磁盘I/O操作,提高性能。
文件映射的工作原理:
通过文件映射,操作系统将磁盘上的文件内容映射到内存中,进程可以直接访问这些映射的内存区域,就像访问普通的内存一样。文件内容并不会立即加载到内存,而是根据访问需要按需加载。这种机制可以减少内存的消耗,尤其是在处理大文件时。
文件映射的关键概念:
- 文件映射对象(File Mapping Object): 文件映射对象是一个在内存中的结构,它表示了文件映射的映射区域。通过文件映射对象,操作系统能够管理文件的内存映射。
- 内存视图(Memory View): 内存视图是应用程序映射到其地址空间的文件的一部分。多个进程可以共享同一个文件映射对象,但每个进程都有自己的内存视图。
文件映射的主要步骤:
- 创建或打开文件映射对象:首先,使用
CreateFileMapping
函数创建一个文件映射对象,或者打开一个已存在的文件映射对象。 - 映射文件到进程的内存:通过
MapViewOfFile
函数将文件的某一部分映射到进程的虚拟地址空间。 - 操作映射的内存:映射到内存中的文件内容可以直接通过指针进行读写。
- 解除映射并关闭文件映射:操作完毕后,通过
UnmapViewOfFile
解除映射,并通过CloseHandle
关闭文件映射对象。
常用API:
CreateFileMapping:创建或打开一个文件映射对象。
1 | HANDLE CreateFileMapping( |
MapViewOfFile:将文件映射到进程的内存空间。
1 | LPVOID MapViewOfFile( |
UnmapViewOfFile:解除文件映射。
1 | BOOL UnmapViewOfFile(LPCVOID lpBaseAddress); |
CloseHandle:关闭文件映射对象。
1 | BOOL CloseHandle(HANDLE hObject); |