1. 程式人生 > >C++:通過C++程式碼簡單理解程序間的通訊機制:共享記憶體

C++:通過C++程式碼簡單理解程序間的通訊機制:共享記憶體

下面用共享對映檔案的方式實現程序間通訊,程式碼可以執行。

一、淺理解

每個程序有自己獨立的空間,一個程序無法訪問其他程序的資料。就好像兩個是互不干涉的個體,想讓它們進行通訊(交換資料),就必須有一段它們都可以訪問到的空間,作為中間介質。在計算機中,可以存放資料的地方分為記憶體和硬碟,程序是執行著的程式,肯定在記憶體當中。為讓程序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.共享記憶體中常存放結構體陣列
        為了在各個程序當中共享資料,常常將自定義的結構體陣列放到共享記憶體中(也就是說共享記憶體經常只存放一個變數,而這個變數是一個結構體陣列)以此實現程序間資料的讀寫。本部落格以字串為例進行儲存,將來有時間再修改程式。