C++:通過C++程式碼簡單理解程序間的通訊機制:共享記憶體
阿新 • • 發佈:2018-11-15
下面用共享對映檔案的方式實現程序間通訊,程式碼可以執行。
一、淺理解
每個程序有自己獨立的空間,一個程序無法訪問其他程序的資料。就好像兩個是互不干涉的個體,想讓它們進行通訊(交換資料),就必須有一段它們都可以訪問到的空間,作為中間介質。在計算機中,可以存放資料的地方分為記憶體和硬碟,程序是執行著的程式,肯定在記憶體當中。為讓程序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 函式得到此記憶體空間的一個對映 , 這樣系統就把同一塊記憶體區域對映到了不同程序的地址空間中 , 從而達到共享記憶體的目的 。
- 先建立一個檔案 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 該檔名可以隨意取。 - 建立記憶體對映檔案 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
:是虛擬檔案的名字/標識,客戶端讀時也用此來作為檔案的標識,所以,這個名字應該是在程序外部註冊的。 - 載入並獲得記憶體對映檔案 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: 對映的記憶體大小 - 使用記憶體,即使用檔案
可以向這個記憶體(檔案)讀寫資料了。
//寫:
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;
- 開啟記憶體對映檔案(虛擬檔案)
HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS,TRUE,TEXT("ZJ"));
通過記憶體對映檔名 ZJ 找到該檔案的。 - 獲得地址
CHAR* pszText = NULL;
pszText = (CHAR*)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,1024*1024);
對映成記憶體 MapViewOfFile和伺服器中的一樣。 - 使用記憶體
printf(pszText); //從檔案中讀(共享記憶體)
讀寫資料,此處寫入資料後,從伺服器中讀出來的內容就改變了。
可以證明,伺服器客戶端同時使用著同一個檔案,檔案是它們之間的一個通道,用來交換資料 - 關閉對映,關閉檔案。同伺服器
UnmapViewOfFile(pszText);
CloseHandle(hMap);
四、注意點
- 伺服器和客戶端必須同時為程序,即都在執行的時候,才可以交換資料。
雖然是通過物理檔案,互動資料的,但是ZJ是虛擬檔案的名字,該名字必須在兩個程序中都能認識,才可以通過它來互動資料。
所以,如果伺服器先開啟,寫入檔案後,關閉。 客戶端,再開啟檔案,則CreateFileMap會失敗,也無法進行檔案映射了。就像網路通訊一樣,必須雙方都在,才可以通訊。
當然客戶端也可以通過CreateFile開啟一個存在的檔案 zj.dat ,再用CreateFileMap去對映,同樣可以訪問檔案中的資料。 - 記憶體對映檔案的機制是單純的讓訪問檔案變的簡單。
就好像檔案流fstream模擬了輸入輸出流iostream一樣,操作檔案,就像操作cout,cin一樣方便。
同理,通過檔案對映,操作檔案就可以向操作記憶體一樣方便。只是將這個機制用於程序間通訊時,才需要考慮一端傳送,一端接收,同步問題等。
3.共享記憶體中常存放結構體陣列
為了在各個程序當中共享資料,常常將自定義的結構體陣列放到共享記憶體中(也就是說共享記憶體經常只存放一個變數,而這個變數是一個結構體陣列)以此實現程序間資料的讀寫。本部落格以字串為例進行儲存,將來有時間再修改程式。