C++:通过C++代码简单理解进程间的通讯机制:共享内存

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dashumak/article/details/83107889

下面用共享映射文件的方式实现进程间通信,代码可以运行。

一、浅理解

每个进程有自己独立的空间,一个进程无法访问其他进程的数据。就好像两个是互不干涉的个体,想让它们进行通信(交换数据),就必须有一段它们都可以访问到的空间,作为中间介质。在计算机中,可以存放数据的地方分为内存和硬盘,进程是运行着的程序,肯定在内存当中。为让进程A和进程B进行通信,它们都可以访问的空间可以是

  • 内存中它们以外的区域
  • 硬盘中的区域

内存文件映射是将硬盘中的一个文件映射到内存中,进程A,B都可以访问该内存(文件),达到交换数据的目的。如右图是给用户的直接感觉,两个进程操作同一个物理文件,通过文件的读写,交换数据。

在这里插入图片描述
在这里插入图片描述

二、发送方(服务器)

个人理解,虽然共享内存都可以读写,也没有服务器和客户端的概念。但是,需要有一方创建这个文件,而另一方只需要获取并打开这个文件。为了方便,将创建文件的一方称作服务器,而获取并打开文件的一方称为客户端。而事实上,服务器或者客户端都可以对文件进行读写,类似于网络编程中的客户端和服务器都可以发消息接消息。自己写的服务器端C++代码如下

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#pragma warning(disable:4996)
int_tmain(intargc, _TCHAR* argv[]){
	HANDLEhFile =CreateFile(TEXT("c:\zj.dat"),GENERIC_READ|GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
	if(hFile==NULL){
		printf("create file error!");
		return0;
	}
	// HANDLE hFile = (HANDLE)0xffffffff; //创建一个进程间共享的对象
	HANDLEhMap = CreateFileMapping(hFile,NULL,PAGE_READWRITE,  0,1024*1024,TEXT("ZJ"));
	intrst = GetLastError();
	if(hMap != NULL && rst == ERROR_ALREADY_EXISTS){
		printf("hMap error\n");
		CloseHandle(hMap);
		hMap = NULL;
		return0;
	}
	CHAR* pszText=NULL;
	pszText = (CHAR*)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,1024*1024);
	if(pszText==NULL){
		printf("view map error!");
		return0;
	}
	sprintf(pszText,"hello my first mapping file!\n"); //其实是向文件中(共享内存中)写入了
	while(1){
		printf(pszText);
		Sleep(3000);
	}
	getchar();
	UnmapViewOfFile((LPCVOID)pszText);
	CloseHandle(hMap);
    CloseHandle(hFile);
	return0;
}

当调用 CreateFileMapping 创建命名的内存映射文件对象时 , Windows 即在物理内存申请一块指定大小的内存区域 , 返回文件映射对象的句柄 hMap ; 为了能够访问这块内存区域必须调用 MapViewOfFile 函数 , 促使 Windows 将此内存空间映射到进程的地址空间中 ; 当在其他进程访问这块内存区域时 , 则必须使用 OpenFileMapping 函数取得对象句柄 hMap , 并调用 MapViewOfFile 函数得到此内存空间的一个映射 , 这样系统就把同一块内存区域映射到了不同进程的地址空间中 , 从而达到共享内存的目的 。

  1. 先创建一个文件 CreateFile
    HANDLE hFile = CreateFile(...);
    参数可参见MSDN,就是创建一般的文件,此处不详说。个人认为这个文件的目的,就是共享内存的实体(也就是对应的硬盘中的区域)。
    CreateFile(TEXT("c:\zj.dat"),GENERIC_READ|GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
         在C盘的确有文件zj.dat 该文件名可以随意取。
  2. 创建内存映射文件 CreateFileMapping
        将上述硬盘中的文件hFile映射成为一个虚拟的映射文件 hMap ,即将物理文件与虚拟文件绑定,或者理解成将硬盘中的文件映射到内存当中。
    HANDLE hMap = CreateFileMapping( hFile, NULL, PAGE_READWRITE, 0,1024*1024,TEXT("ZJ"));
         参数解释: hFile是对应的物理文件的句柄。如果hFile=NULL,即没有通过CreateFile创建一个实际存在的文件。有解释为创建一个进程间共享的对象。个人认为也可能是在内存中随机开辟了一段空间,或者在硬盘上有一个默认文件。
    NULL: 安全属性
    PAGE_READWRITE: 可读可写
    0, 1024*1024: 从物理文件的高0位到低,1024*1024为映射成虚拟文件。(个人是这样理解的)
    ZJ :是虚拟文件的名字/标识,客户端读时也用此来作为文件的标识,所以,这个名字应该是在进程外部注册的。
  3. 加载并获得内存映射文件 MapViewOfFile 在内存中的地址
         将虚拟文件映射成内存地址,方便使用。即将文件与内存绑定,以后由此地址访问该块内存。
    CHAR* pszText=NULL; //一个指针,不需要分配空间
    pszText = (CHAR*)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,1024*1024); //通过映射后,该指针就指向该文件。
        参数解释:hMap作为虚拟文件的标识(通俗点就是虚拟文件名);
    FILE_MAP_ALL_ACCESS:设定访问模式;
    0,0:从虚拟文件的哪个位置开始映射成内存
    1024*1024: 映射的内存大小
  4. 使用内存,即使用文件
        可以向这个内存(文件)读写数据了。
    //写:
    sprintf(pszText,"hello my first mapping file!\n");
        语句本身的意思,是将句子写入字符串pszText中,而这个字符串并没有在程序中分配空间,即没有new。但这句话也不会报错。是因为该字符串地址指向了映射文件,即通过操作该指针,实际是操作了文件,句子写入了文件当中。
    //读:
    printf(pszText);
        语句本身是将字符串中的内容输出到屏幕上,该字符串中没有分配空间,也没有赋值。但通过映射为文件,可以将文件中的内容通过该指针输出到屏幕上。
         注:个人理解,可以认为该指针pszText直接指向了硬盘空间(即通过内存文件和硬盘文件直接联系在一起)。就是文件的操作符。
    5. 载映射 :
    UnmapViewOfFile((LPCVOID)pszText);//卸载映射
    6. 关闭文件:
    "__mceDel">CloseHandle(hMap); //关闭虚拟文件
    "__mceDel">CloseHandle(hMap); //关闭虚拟文件

三、接收方(客户端)

HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS,TRUE,TEXT("ZJ"));  
if(hMap == NULL){        
printf("open file map error!");
return0;
}    
CHAR* pszText = NULL;    
pszText = (CHAR*)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,1024*1024);    if(pszText==NULL){        
printf("map view error!\n");        
return0;    
}    
printf(pszText); //从文件中读(共享内存)         
sprintf(pszText,"second data!\n"); //写入    
getchar();    
UnmapViewOfFile(pszText);    
CloseHandle(hMap);    
hMap = NULL;     
return0;
  1. 打开内存映射文件(虚拟文件)
    HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS,TRUE,TEXT("ZJ"));
    通过内存映射文件名 ZJ 找到该文件的。
  2. 获得地址
    CHAR* pszText = NULL;
    pszText = (CHAR*)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,1024*1024);
        映射成内存 MapViewOfFile和服务器中的一样。
  3. 使用内存
    printf(pszText); //从文件中读(共享内存)
        读写数据,此处写入数据后,从服务器中读出来的内容就改变了。
        可以证明,服务器客户端同时使用着同一个文件,文件是它们之间的一个通道,用来交换数据
  4. 关闭映射,关闭文件。同服务器
    UnmapViewOfFile(pszText);
    CloseHandle(hMap);

四、注意点

  1. 服务器和客户端必须同时为进程,即都在运行的时候,才可以交换数据。
        虽然是通过物理文件,交互数据的,但是ZJ是虚拟文件的名字,该名字必须在两个进程中都能认识,才可以通过它来交互数据。
         所以,如果服务器先打开,写入文件后,关闭。 客户端,再打开文件,则CreateFileMap会失败,也无法进行文件映射了。就像网络通信一样,必须双方都在,才可以通信。
    当然客户端也可以通过CreateFile打开一个存在的文件 zj.dat ,再用CreateFileMap去映射,同样可以访问文件中的数据。
  2. 内存映射文件的机制是单纯的让访问文件变的简单。
        就好像文件流fstream模拟了输入输出流iostream一样,操作文件,就像操作cout,cin一样方便。
        同理,通过文件映射,操作文件就可以向操作内存一样方便。只是将这个机制用于进程间通信时,才需要考虑一端发送,一端接收,同步问题等。
    3.共享内存中常存放结构体数组
        为了在各个进程当中共享数据,常常将自定义的结构体数组放到共享内存中(也就是说共享内存经常只存放一个变量,而这个变量是一个结构体数组)以此实现进程间数据的读写。本博客以字符串为例进行存储,将来有时间再修改程序。

猜你喜欢

转载自blog.csdn.net/dashumak/article/details/83107889