进程通信
进程是操作系统分配资源和调度的独立单位,由进程内核对象和进程内存地址空间构成。不同的进程内存地址空间相互隔离,因此一个进程无法直接访问另一个进程的内存数据。当进程间需要数据共享或任务协作时,就必须使用进程通信。进程通信是指在不同进程之间的数据传输。简单的说进程通信就是多进程共享一份内存空间。
接下来我们讲解几种不同方式的进程通信
文件映射
文件映射:通过创建一个文件映射对象,将文件的部分或全部内容映射在一个或多个进程的虚拟地址空间中,使得这些进程可以像访问普通内存一样访问文件内容。多个进程可以直接对该映射的内存区域进行读写操作从而实现进程间的数据共享和通信。
现在我们创建两个进程,来演示如何进行文件映射进程通信
进程一:
// process1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
#define BUF_SIZE 256
TCHAR szName[] = L"rkvirObject";//进程一二约定好的文件映射对象名
int main()
{
HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, szName);//进程一创建一个文件映射对象,
//进程一般在32位地址范围下进行通信
LPVOID lpBuffer = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);//将文件映射对象映射在进程一的虚拟地址空间中
CopyMemory(lpBuffer, L"Hello File Map", ((wcslen(L"Hello File Map") + 1) * 2));//在该映射进行读写操作
getchar();
UnmapViewOfFile(lpBuffer);//解除映射
CloseHandle(hMapFile);//关闭句柄
system("pause");
return 0;
}
进程二:
// process2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
#define BUF_SIZE 256
TCHAR szName[] = L"rkvirObject";//进程一二约定好的文件映射对象
int main()
{
HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, szName);//打开进程一创建的文件映射对象
LPVOID lpBuffer = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);//获取文件映射对象在进程一的映射
printf("%S", lpBuffer);//打印共享映射中的数据
MessageBox(NULL, (LPCWSTR)lpBuffer, L"success", MB_OK);
UnmapViewOfFile(lpBuffer);//解除文件映射
CloseHandle(hFileMap);//关闭句柄
system("pause");
return 0;
}
首先运行进程一,创建文件映射。然后运行进程二,使用进程一创建的文件映射。由上述代码运行结果可知,进程二打印了进程一在文件映射中存储的数据
命名管道
管道是共享内存的一部分,用于进程之间的通信。从本质上说管道是一种存储在文件系统之外的特殊文件,没有名称或任何其他特定属性,但它有两个文件描述符,因此我们可以像处理文件一样处理它。
Windows操作系统中的管道通信通过内核空间中的缓冲区来暂存从一个进程输出但另一个进程尚未读取的数据。这些缓冲区完全由操作系统的内核控制,并且与任何特定进程的用户空间内存隔离。因此进程间数据的写入和读取,实际是依靠内核空间的缓冲区。
Windows支持两种类型的管道:匿名管道和命名管道。
命名管道:也被称为 FIFO,是一种特殊类型的文件,允许不同进程通过这个特殊文件进行数据通信。与匿名管道不同,命名管道可以在不相关的进程之间进行通信,而不仅仅局限于具有亲缘关系的进程(如父子进程)。
接下来我们通过两个进程实现命名管道通信
进程一:
// process1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
int main()
{
HANDLE hPipe = CreateNamedPipe(L"\\\\.\\pipe\\Communication", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, 1024, 1024, 0, NULL);//创建命名管道实例 注意转义
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//创建匿名事件,用于线程同步
OVERLAPPED ovlap;
ZeroMemory(&ovlap, sizeof(ovlap));//清空一段内存
ovlap.hEvent = hEvent;//设置匿名事件
if (!ConnectNamedPipe(hPipe,&ovlap))//连接管道和内存
{
//当连接失败时,判断错误原因
if (GetLastError() != ERROR_IO_PENDING)//ERROR_IO_PENDING是一种特殊的错误,并不会影响程序的运行,因此当连接出现该错误时可以忽略,而其他错误需要处理
{
//当发生其他错误时的相关处理
std::cout << "ConnectNamedPipe Error Code:" << GetLastError() << std::endl;
CloseHandle(hPipe);
CloseHandle(hEvent);
return -1;
}
}
if (WAIT_FAILED == WaitForSingleObject(hEvent,INFINITE))//检查匿名事件是否满足
{
std::cout << "WAIT_FAILED Error Code:" << GetLastError() << std::endl;
CloseHandle(hPipe);
CloseHandle(hEvent);
return -1;
}
//当匿名事件满足时
CloseHandle(hEvent);//关闭匿名事件,此时管道已经接收到事件
char szBuffer[0x100] = { 0 };
DWORD dwReadSize = 0;
ReadFile(hPipe, szBuffer, 0x100, &dwReadSize, NULL);//读取管道中内容
std::cout << szBuffer << std::endl;//打印管道中的内容
char WriteBuffer[] = "Hello";
DWORD dwWriteSize = 0;
WriteFile(hPipe, WriteBuffer, strlen(WriteBuffer) + 1, &dwWriteSize, NULL);//在管道中写内容
system("pause");
return 0;
}
进程二:
// process2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
int main()
{
if (!WaitNamedPipe(L"\\\\.\\pipe\\Communication",NMPWAIT_WAIT_FOREVER))//等待命名管道的实例变为可用状态
{
std::cout << "WaitNamedPipe Error Code:" << GetLastError() << std::endl;
return -1;
}
HANDLE hPipe = CreateFile(L"\\\\.\\pipe\\Communication", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//打开进程一创建的管道
char WriteBuffer[] = "Hello";
DWORD dwWriteSize = 0;
WriteFile(hPipe, WriteBuffer, strlen(WriteBuffer) + 1, &dwWriteSize, NULL);//在管道中写数据
char szBuffer[0x100] = { 0 };
DWORD dwReadSize = 0;
ReadFile(hPipe, szBuffer, 0x100, &dwReadSize, NULL);//在管道中读数据
std::cout << szBuffer << std::endl;//此时进程二在管道中写入的数据连同进程一在管道中写入的数据一起被输出
system("pause");
return 0;
}
邮槽
进程一
#include <iostream>
#include <Windows.h>
int main()
{
char szBuffer[] = "Hello";
HANDLE hMailslot = INVALID_HANDLE_VALUE;//创建一个邮槽句柄,初始化为无效值
hMailslot = CreateFile(L"\\\\.\\mailslot\\Communication", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//打开邮槽
DWORD dwWriteSize = 0;
WriteFile(hMailslot, szBuffer, strlen(szBuffer) + 1, &dwWriteSize, NULL);//向邮槽写数据
CloseHandle(hMailslot);
system("pause");
return 0;
}
进程二
#include <iostream>
#include <Windows.h>
char szBuffer[MAX_PATH] = { 0 };
int main()
{
HANDLE hMailslot = CreateMailslot(L"\\\\.\\mailslot\\Communication", 0, MAILSLOT_WAIT_FOREVER, NULL);//创建一个命名邮槽
DWORD dwReadSize = 0;
if (!ReadFile(hMailslot, szBuffer,MAX_PATH,&dwReadSize,NULL))//读取邮槽内容
{
CloseHandle(hMailslot);
return -1;
}
std::cout << szBuffer << std::endl;
CloseHandle(hMailslot);
system("pause");
return 0;
}
同时运行进程一和进程二,我们发现进程二打印了进程一写入邮槽的数据
剪切板
剪切板是Windows自带的剪切板
进程一
#include <iostream>
#include <Windows.h>
int main()
{
const char * pStr = "This is a string!";
if (OpenClipboard(NULL))//打开剪切板
{
char * szBuffer = NULL;
EmptyClipboard();//清空剪切板
HGLOBAL hGlobalClip = GlobalAlloc(GHND, strlen(pStr) + 1);//申请堆内存用于存储数据
//由于申请的堆内存是可移动的,因此只有锁定该堆内存使其不可移动才能获取该堆内存地址
szBuffer = (char *)GlobalLock(hGlobalClip);//获取堆内存地址
strcpy(szBuffer, pStr);//写数据到堆内存中
//当使用完堆内存时,需要将其解锁以便操作系统对其进行管理
GlobalUnlock(hGlobalClip);//解锁堆内存
SetClipboardData(CF_TEXT, hGlobalClip);//设置剪切板数据格式并向剪切板输入数据
CloseClipboard();
}
system("pause");
return 0;
}
进程二
#include <iostream>
#include <Windows.h>
int main()
{
if (OpenClipboard(NULL))//打开剪切板
{
if (IsClipboardFormatAvailable(CF_TEXT))//检查剪切板数据格式
{
HGLOBAL hGlobalClip = GetClipboardData(CF_TEXT);//获取剪切板中的数据
char * szBuffer = NULL;
szBuffer = (char *)GlobalLock(hGlobalClip);//申请堆内存用于保存剪切板中的数据
GlobalUnlock(hGlobalClip);//解锁堆内存
std::cout << szBuffer << std::endl;//输出剪切板数据
}
CloseClipboard();//关闭剪切板
}
system("pause");
return 0;
}
运行进程一二,进程二打印了进程一写进剪切板的数据