共享内存--是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
在linux下使用的是shmget,shmat,shmdt等函数,所以在网上看到这类的,基本就是在介绍linux环境下的实现,windows下不适用,但可以看其原理。
0.传输数据的结构定义
struct Splayer
{
int numid;
int age;
char sex;
char name[128];
char ip[256];
int port;
};
上面结构的定义较为简单,仅为了测试使用。
具体实现步骤如下: (http://www.jb51.net/article/52306.htm)
1)、在服务器端进程中调用内存映射API函数CreateFileMapping创建一个有名字标识的共享内存;
函数CreateFileMapping原型如下:
2)、在创建文件映射对象后,服务器端进程调用MapViewOfFile函数映射到本进程的地址空间内;
3)、客户端进程访问共享内存对象,需要通过内存对象名调用OpenFileMapping函数,以获得共享内存对象的句柄
4)、如果客户端进程获得共享内存对象的句柄成功,则调用MapViewOfFile函数来映射对象视图。用户可以使用该对象视图来进行数据读写操作,以达到数据通讯的目的。
5)、当用户进程结束使用共享内存后,调用UnmapViewOfFile函数以取消其地址空间内的视图:
FileMapping用于将存在于磁盘的文件放进一个进程的虚拟地址空间,并在该进程的虚拟地址空间中产生一个区域用于“存放”该文件,这个空间就叫做File View(存放在进程的虚拟内存中),系统并同时产生一个File Mapping Object(存放于物理内存中)用于维持这种映射关系,这样当多个进程需要读写那个文件的数据时,它们的File View其实对应的都是同一个File Mapping Object,这样做可节省内存和保持数据的同步性,并达到数据共享的目的。
1.我们会创建两个程序,一个称之服务端,一个称之客户端。
服务端:(1.)创建共享内存区域 (2.)内存映射到当前进程 (3.)写入数据
客户端:(1.)打开共享内存区域 (2.)内存映射到当前进程 (4.)读出数据
2.服务端
void CMySharedServerDlg::MySharedMemory()
{
/*
struct Splayer
{
int numid;
int age;
char sex;
char name[128];
char ip[256];
int port;
};
*/
//1.创建共享内存区域
HANDLE hfile = ::CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,65536,"GameQueen");
if(NULL==hfile)
{
AfxMessageBox("创建失败!",MB_OK,0);
return;
}
//2.内存映射
PVOID pview = NULL;
pview = ::MapViewOfFile(hfile,FILE_MAP_ALL_ACCESS,0,0,1024);
if (NULL==pview)
{
AfxMessageBox("映射失败!",MB_OK,0);
return;
}
//char* pMessage = "皇后come from MySharedServerDlg!";//单独字符串也可传输
Splayer player;
player.numid = 6666;
player.age = 24;
player.sex = '1';
strcpy(player.name,"tangwei");
strcpy(player.ip,"192.168.1.102");
player.port = 8080;
DWORD dmessage = sizeof(Splayer);
memcpy_s(pview,1024,&player,dmessage);
}
上面是源码,简诉下过程:
(1)、创建一个特定大小的文件映射对象,名称为“GameQueen”,也可以宏来定义。
(2)、将这个对象的文件视图映射到进程的地址空间,然后向视图中写入数据/字符串。
(3)、函数参数分析
// Create the file mapping object.
hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // Use paging file - shared memory
NULL, // Default security attributes
PAGE_READWRITE, // Allow read and write access
0, // High-order DWORD of file mapping max size
MAP_SIZE, // Low-order DWORD of file mapping max size
FULL_MAP_NAME // Name of the file mapping object
);
pView = MapViewOfFile(
hMapFile, // Handle of the map object
FILE_MAP_ALL_ACCESS, // Read and write access
0, // High-order DWORD of the file offset
VIEW_OFFSET, // Low-order DWORD of the file offset
VIEW_SIZE // The number of bytes to map to view
);
3.客户端
源码:
void CMyShareClientDlg::MySharedMemory()
{
HANDLE hfile = OpenFileMapping(FILE_MAP_READ,FALSE,"GameQueen");
if (NULL==hfile)
{
AfxMessageBox("不能打开共享内存",MB_OK,0);
return;
}
PVOID pview;
pview = ::MapViewOfFile(hfile,FILE_MAP_READ,0,0,1024);
if (NULL==pview)
{
AfxMessageBox("不能打开2",MB_OK,0);
return;
}
//char* pStr = (char*)pview;
//CString str(pStr);
//OutputDebugString(str);
Splayer *player = (Splayer*)pview;//上面读取字符串,此处是读取结构体信息
}
过程分析:
(1.)执行客户程序CMyShareClientDlg
(2.)打开这个名称为“GameQueen”的文件映射对象
(3.)然后把相同的文件映射视图映射到自己的地址空间中
(4.)然后从视图中读取服务进程所写入的数据。
4.截图
服务端发送数据:
客户端接收数据:
5.注意
(1.)由于事件,信号,互斥对象和文件映射等这些内核对象都共享同一个名字空间,所以如果这个名字和其他一个对象的名称重名的话那么将创建失败。
(2.)为了实现共享内存,进程应首先调用CreateFileMapping函数然后在hFile参数中传入INVALID_HANDLE_VALUE宏来替代句柄。相应的文件映射对象会从系统的分页文件中获得一段内存。如果hFile参数的值是INVALID_HANDLE_VALUE,那么你在调用CreateFileMapping时必须给共享内存指定一个大小值。
2.
int main()
{
string strMapName("ShareMemory"); // 内存映射对象名称
string strComData("This is common data!"); // 共享内存中的数据
LPVOID pBuffer; // 共享内存指针
// 首先试图打开一个命名的内存映射文件对象
HANDLE hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, (LPCWSTR)strMapName.c_str());
if (NULL == hMap)
{ // 打开失败,创建之
hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
strComData.length() + 1,
(LPCWSTR)strMapName.c_str());
//如果有问题用L"name"形式
// 映射对象的一个视图,得到指向共享内存的指针,设置里面的数据
pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
strcpy((char*)pBuffer, strComData.c_str());
cout << "写入共享内存数据:" << (char *)pBuffer << endl;
}
else
{ // 打开成功,映射对象的一个视图,得到指向共享内存的指针,显示出里面的数据
pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
cout << "读取共享内存数据:" << (char *)pBuffer << endl;
}
getchar(); // 注意,进程关闭后,所有句柄自动关闭,所以要在这里暂停
// 解除文件映射,关闭内存映射文件对象句柄
::UnmapViewOfFile(pBuffer);
::CloseHandle(hMap);
system("pause");
return 0;
return 0;
}