프로세스간에 데이터를 공유할 때..
많이 사용되는 방법중에 하나가 공유 메모리(Memory Mapped File)를 사용하는 것이다.
일단 서버에서는 읽고/쓰기 모드로 열어서 내부에서 사용하고..
다른 한쪽 혹은 여러클라이언트 에서는 읽기 전용 모드로 열어서 사용하는 가장 간편한 방법을 사용해보자.
(사용하는 방식은 메모리를 액세스하는것과 동일하다..)
위 그림처럼, 하나의 메모리 공간을 양쪽에서 액세스 할 수 있도록 제공해주는 점이 탁월한 장점이고,
또한 멀티 쓰레드? 멀티 코어? 와 상관없이...
서버는 읽기/쓰기 규칙을 지키고, 클라이언트는 읽기만 한다면 절대 공유 액세스 위반을 걱정할 필요가 없다.
우선 사용되는 API를 살펴보자. 아래에 해당하는 5개의 API만 사용해도 되며,
CreateFileMapping 함수의 첫번째(hFile) 인자와, 여섯번째(lpName) 인자만 주의하면 특별하게
인자에 신경쓰지 않아도 된다.
HANDLE WINAPI CreateFileMapping(
__in HANDLE hFile,
__in LPSECURITY_ATTRIBUTES lpAttributes,
__in DWORD flProtect,
__in DWORD dwMaximumSizeHigh,
__in DWORD dwMaximumSizeLow,
__in LPCTSTR lpName
);
※ HANDLE hFile
사용자가 이미 만들어 놓은 파일 핸들이나, INVALID_HANDLE_VALUE 를 전달할 수 있으며, INVALID_HANDLE_VALUE 이
전달되었을 경우는 시스템의 페이지 파일을 할당해서 사용한다.
그러므로, 특별하게 큰 파일을 사용하는 경우가 아니라면 INVALID_HANDLE_VALUE 만으로도 충분하다.
※ LPCTSTR lpName
해당 공유 메모리를 확인하기 위한 고유한 이름을 나타낸다. 일반적인 클라이언트 OS 에서는 별 문제가 없겠지만
서버 OS에 Terminal Service 가 구동중이라면 Prefix 에 따라 그 특성이 달라진다.
모든 세션에서 접근할 수 있으려면 Global\이름 과 같이 Global\ 라는 Prefix를 붙혀주어야 한다.
HANDLE WINAPI OpenFileMapping(
__in DWORD dwDesiredAccess,
__in BOOL bInheritHandle,
__in LPCTSTR lpName
);
LPVOID WINAPI MapViewOfFile(
__in HANDLE hFileMappingObject,
__in DWORD dwDesiredAccess,
__in DWORD dwFileOffsetHigh,
__in DWORD dwFileOffsetLow,
__in SIZE_T dwNumberOfBytesToMap
);
BOOL WINAPI UnmapViewOfFile(
__in LPCVOID lpBaseAddress
);
BOOL WINAPI CloseHandle(
__in HANDLE hObject
);
서버쪽 프로그램 파트
HANDLE hMemoryMap = NULL;
LPBYTE pMemoryMap = NULL;
hMemoryMap = ::CreateFileMapping(
(HANDLE)0xffffffff, // 파일 맵의 핸들, 초기에 0xffffffff를 설정한다.
NULL, // 보안 속성
PAGE_READWRITE, // 읽고/쓰기 속성
0, // 64비트 어드레스를 사용한다. 상위 32비트 - 메모리의 크기
sizeof(MYSTRUCT),// 하위 32비트 - 여기선LPBYTE 타입.
L"fork_server");// 공유 파일맵의 이름 - Uique 해야한다.
if(!hMemoryMap) {
::MessageBox(NULL, L"공유 메모리를 생성할 수 없습니다.", L"Error", MB_OK);
return FALSE;
}
pMemoryMap = (BYTE*)::MapViewOfFile(
hMemoryMap, // 파일맵의 핸들
FILE_MAP_ALL_ACCESS, // 액세스 모드 - 현재는 쓰기
0, // 메모리 시작번지부터의 이격된 상위 32비트
0, // 메모리 시작번지부터의 이격된 하위 32비트
0); // 사용할 메모리 블록의 크기 - 0이면 설정한 전체 메모리
if(!pMemoryMap)
{
CloseHandle(hMemoryMap);
::MessageBox(NULL, L"공유 메모리를 열수 없습니다.", L"Error", MB_OK);
return FALSE;
}
__int64 check = 0;
MYSTRUCT* pStruct = (MYSTRUCT*)pMemoryMap;
while(1)
{
pStruct->a = check;
pStruct->b = 0.1f;
pStruct->c = 0.0000001;
strcpy(pStruct->buffer, "data copy");
if(check++%100000 == 0)
{
if(_kbhit() != 0)
break;
}
}
if(pMemoryMap)
UnmapViewOfFile(pMemoryMap);
if(hMemoryMap)
CloseHandle(hMemoryMap);
클라이언트 프로그램 파트
HANDLE hMemoryMap = NULL;
LPBYTE pMemoryMap = NULL;
hMemoryMap = ::OpenFileMapping(FILE_MAP_READ, FALSE, L"fork_server");
if(!hMemoryMap) {
::MessageBox(NULL, L"공유 메모리를 생성할 수 없습니다.", L"Error", MB_OK);
return FALSE;
}
pMemoryMap = (BYTE*)::MapViewOfFile(hMemoryMap,FILE_MAP_READ,0,0,0);
if(!pMemoryMap)
{
CloseHandle(hMemoryMap);
::MessageBox(NULL, L"공유 메모리를 열수 없습니다.", L"Error", MB_OK);
return FALSE;
}
char buffer[64];
__int64 check = 0;
MYSTRUCT* pStruct = (MYSTRUCT*)pMemoryMap;
while(1)
{
int a = pStruct->a;
float b = pStruct->b;
double c = pStruct->c;
strcpy(buffer, pStruct->buffer);
if(check++%100000 == 0)
{
if(_kbhit() != 0)
break;
}
}
if(pMemoryMap)
UnmapViewOfFile(pMemoryMap);
if(hMemoryMap)
CloseHandle(hMemoryMap);
ShareServer.zip
ShareClient.zip
※ 참고로, 서버프로그램이 종료되었을 경우, 클라이언트에서 메모리 맵드 파일의 현황만 가지고는 알수 없다는 단점이 있다.
즉, 서버가 죽어도 클라이언트가 살아서 데이터를 읽는데는 전혀 문제가 없기 때문에.. 이러한 부분은 별도로 확인해 주어야한다.