使用内存映射文件实现进程通信-文件句柄

内存映射文件技术概述

内存映射文件,是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

Win32系统允许多个进程(运行在同一计算机上)使用内存映射文件来共享数据,所以,如果一个进程往内存映射文件中写数据,另一个进程来读,就实现了它们之间的通信。实际上,其他共享和传送数据的技术,诸如使用SendMessage或者PostMessage,都在内部使用了内存映射文件。

实现

接下来我们通过C语言编写程序,实现两个进程交换数据(通信)。在本例中,只在同一台主机上实现不同进程间的通信,所以可以使用内存映射文件来实现。

程序基本流程:

1) 新建命名共享

首先利用CreateFile或者CreateFileForMapping获得一个用于映射的物理文件句柄, 然后利用该文件句柄结合CreateFileMapping得到一个命名的共享内存映射文件句柄。

2) 打开命名共享内存

如果需要共享已经存在的命名共享内存映射文件, 使用OpenFileMapping函数。

3) 获得地址空间指针

进行内存映射文件的读写和一般的文件读写不同, 是直接面对你申请的地址空间, 为此需要使用MapViewOfFile得到相关的地址LPVOID类型的指针。 如果需要进行数据写入, 可以通过类型转换直接对于内存地址进行赋值,比如memcpy( lpAddress, lpBuf, ....)如果是读取操作,将参数顺序调整一下就可以了。

4) 将内存复制到所映射的物理文件上面

FlushMapViewOfFile函数可以将内存里面的内容DUMP到物理磁盘上面。

5) 卸载内存映射文件地址指针

UnmapViewOffFile函数来卸内存映射文件。

6) 关闭内存映射文件

最后,调用CloseHandle来关闭内存映射文件。

实现及测试源代码

两个进程一个视为客户端,另一个视为服务端

cilentProcess:

#include "windows.h"

#include "math.h"

#include "stdio.h"

void main()

{

HANDLE hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,"ClientProcess");

if (hFileMapping == NULL)

{

CloseHandle(hFileMapping);

hFileMapping = NULL;

return ;

}

// 设定大小、偏移量等参数

SYSTEM_INFO sinf;

GetSystemInfo(&sinf);

__int64 qwFileSize = 0x4000000;

__int64 qwFileOffset = 0;

__int64 T = 600 * sinf.dwAllocationGranularity;

DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity;

// 将文件数据映射到进程的地址空间

PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping,

FILE_MAP_ALL_ACCESS,

(DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock);

// 测试程序

// 在客户端输入一个数字,在服务端计算该数的平方,并将结果返回给客户端;

int dataInClientPro = 0;

printf("**********In ClintProcess**********\n");

while(1)

{

printf("Please input a num (0 for quit):\t\n");

scanf("0", &dataInClientPro);

if(dataInClientPro == 0)

{

return;

}

memcpy(pbFile,&dataInClientPro,sizeof(int));

memcpy(pbFile+sizeof(int),&dataInClientPro,sizeof(int));

FlushViewOfFile(pbFile,NULL);

Sleep(1000);

memcpy(&dataInClientPro,pbFile+sizeof(int),sizeof(int));

printf("Result form ServerProcess :\t0\n", dataInClientPro);

}

}


serverProcess:

#include "windows.h"

#include "math.h"

#include "stdio.h"

void main()

{

// 创建文件内核对象,其句柄保存于hFile

HANDLE hFile = CreateFile("Recv1.zip",

GENERIC_WRITE | GENERIC_READ,

FILE_SHARE_READ,

NULL,

CREATE_ALWAYS,

FILE_FLAG_SEQUENTIAL_SCAN,

NULL);

// 创建文件映射内核对象,句柄保存于hFileMapping

HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE,

0, 0x4000000, "ClientProcess");

if (hFileMapping != NULL && GetLastError() == ERROR_ALREADY_EXISTS)

{

CloseHandle(hFileMapping);

hFileMapping = NULL;

return ;

}

// 设定大小、偏移量等参数

SYSTEM_INFO sinf;

GetSystemInfo(&sinf);

__int64 qwFileSize = 0x4000000;

__int64 qwFileOffset = 0;

__int64 T = 600 * sinf.dwAllocationGranularity;

DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity;

// 将文件数据映射到进程的地址空间

PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping,

FILE_MAP_ALL_ACCESS,

(DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock);

// 测试程序

// 从客户端读入一个数字,在服务端计算该数的平方,并将结果返回给客户端;

int dataInServerPro;

int flag = 0;

memset(pbFile, 0, sizeof(int));

FlushViewOfFile(pbFile,NULL);

while(1)

{

memcpy(&flag, pbFile, sizeof(int));

if(flag)

{

memcpy(&dataInServerPro, pbFile+sizeof(int), sizeof(int));

printf("**********In ServerProcess**********\nRead a num from ClintProcess :\t0\n",dataInServerPro);

dataInServerPro = dataInServerPro*dataInServerPro;

memcpy(pbFile+sizeof(int),&dataInServerPro,sizeof(int));

FlushViewOfFile(pbFile,NULL);

memset(pbFile, 0,sizeof(int));

FlushViewOfFile(pbFile, NULL);

}

Sleep(1);

}

// 将文件数据从进程的地址空间中释放

UnmapViewOfFile(pbFile);

// 释放文件映射内核对象

CloseHandle(hFileMapping);

// 释放文件内核对象

CloseHandle(hFile);

}


运行结果

使用内存映射文件实现进程通信

推荐阅读