티스토리 뷰
메일슬롯 (우편함)
우편함에 편지를 넣어두면 우편함 주인이 편지를 받을 수 있는 것처럼 두 프로세스가 메일슬롯을 통해 메시지를 주고받을 수 있다.
우편함은 본질적으로 수신을 위한 장치이므로 메시지를 받는 쪽이 서버가 되고 메시지를 보내는 쪽이 클라이언트가 된다.
서버는 메일슬롯을 만들고 메일슬롯을 통해 메시지를 받는 프로세스이다.
CraeteMailSlot()로 메일슬롯을 만들면 메일슬롯 핸들을 리턴 받는데 이 핸들을 사용하여 메시지를 받을 수 있다.
클라이언트는 메일슬롯에 메시지를 쓰는 프로세스이다.
메시지를 한번에 여러개를 보낼 수 있으며 이진 스트림이므로 어떠한 형태의 메시지든 주고받을 수 있다. 단 메시지의 크기는 64K까지만 가능하다. 메일슬롯 이름은 두 프로세스가 서로 인지할 수 있어야 하므로 문자열 형태로 되어 있으며 다음과 같은 형태로 작성된다.
\\.\\mailsolt 다음에 원하는 임의의 이름을 주되 메일슬롯이 많이 사용된다면 목적에 따라 그룹을 나눌 수 있도록 경로를 줄 수 있다. 마치 파일들을 서브 디렉토리로 나누듯이 말이다.예로 채팅요은 \\.\mailslot\chat\xxx
\\.\mailslot\file\yyy
특정 컴퓨터의 메일슬롯에 메시지를 보내려면 \\컴퓨터이름\mailslot\chat\xxx 라고 해야 한다.
특정 도메인에 속한 모든 메일슬롯에 메시지를 보내거나 시스템의 프라이머리 도메인에 속한 모든 메일슬롯에 메시지를 보내려면 \\도메인이름\mailslot\chat\xxx 라고 해야 한다.
메일슬롯 서버
메일슬롯을 만들고 핸들을 소유하며 메시지를 받는 프로세스 이다. 메시지를 받기 위해서는 우선 메일슬롯을 만들어 놓고 메시지를 대기해야 하는데 이때는 다음 함수를 사용한다.
HANDLE CreateMailSolt(LPCTSTR lpName, DOWRD nMaxMessageSize, DWORD lReadTimeout, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
lpNAme : 메일슬롯 이름 (중복 피해야 한다)
nMaxMessageSize : 메일슬롯에 넣을 수 있는 메시지의 최대 길이, 0 이면 무제한, 통상 0으로 지정.
lReadTimeout : 메일스롯에 메시지가 없을 경우 얼마나 기다릴 것인가 1/1000초 단위로 지정.
이 값이 0이고 메시지가 없으면 더 이상 기다리지 않고 즉각 리턴하며
MAILSLOT_WAIT_FOREVER 이면 메시지가 들어올 때까지 무한 대기한다.
lpSecurityAttributes : 보안 속성, NULL이면 기본 보안 속성을 사용한다.
만약 메일슬롯 생성에 실패했다면 INVALID_HANDLE_VALUE 값을 리턴한다. 메일슬롯을 만든 후에 타임아웃 값을 변경하고자 할때 아래 함수를 사용한다.
BOOL SetMailslotInfo(HANDLE hMailslot, DWORD lReadTimeout);
메일슬롯에서 메시지를 읽는 함수
BOOL GetMailslotInfo(HANDLE hMailslot, LPDWORD lpMaxMessageSize, LPDWORD lpNextSize, LPDWORD lpMessageCount, LPDWORD lpReadTimeOut);
hMailslot : 메시지를 읽을 메일슬롯 핸들
lpMaxMessageSize : 메시지 최대 길이
lpNextSize : 실제 읽은 메시지 크기
lpMessageCount : 대기중인 메시지의 수
lpReadTimeOut : 타임아웃 값
필요없는 경우 모두 NULL 지정가능하고 lpNextSize 에 MAILSLOT_NO_MESSAGE 가 리턴되면 전송된 메시지가 없다는 뜻이며 이 함수는 메일슬롯의 메시지를 내부 버퍼에 읽기만 한다. 실제 메시지를 가져오기 위해서는 ReadFile()ㄹ를 사용하되 메일슬롯을 파일처럼 취급하여 파일로 부터 데이터를 읽듯이 읽어내기만 하면 된다.
메일슬롯은 사실 메모리상에 구현된 가상의 파일로 취급되기 때문에 읽고 쓰는 데는 표준 파일 입출력 함수를 그대로 사용할 수 있다. ReadFile(), WriteFile(), CloseHandle() 등은 물론이고 GetFileTime(), GethandleInformation()등의 정보 조사 함수도 메일슬롯과 함께 쓸 수 있다. 단, 메일슬롯은 디스크 상의 파일과는 달리 메모리 상에만 존재하므로 핸들을 닫으면 메일슬롯에 저장된 모든 메시지도 삭제된다는 점만 다르다.
프로젝트가 멀티바이트인 경우 상관없으나 유니코드면 아래 클라이언트 코드 18행에서 (lstrlen(lpMsg) + 1) * 2 로 반드시 2배로 해야 메시지가 안 잘린다. 그렇지 않으면 아래 그림처럼 메시지 잘린다.
서버 코드>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 |
HANDLE hMail;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
DWORD cbMsg, cbRead;
TCHAR lpBuffer[256];
TCHAR* Msg = TEXT("왼쪽 버튼을 누르면 메시지를 받습니다.");
switch (message)
{
case WM_CREATE:
hMail = CreateMailslot(TEXT("\\\\.\\mailslot\\Test"), 0, 0, NULL);
if (hMail == INVALID_HANDLE_VALUE)
MessageBox(hWnd, TEXT("메일슬롯 만들기 실패"), TEXT("에러"), MB_OK);
break;
case WM_LBUTTONDOWN:
GetMailslotInfo(hMail, NULL, &cbMsg, NULL, NULL);
if (cbMsg == MAILSLOT_NO_MESSAGE)
{
MessageBox(hWnd, TEXT("대기중인 메시지가 없습니다."), TEXT("에러"), MB_OK);
return 0;
}
ReadFile(hMail, lpBuffer, cbMsg, &cbRead, NULL);
MessageBox(hWnd, lpBuffer, TEXT("읽은 메시지"), MB_OK);
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다.
TextOut(hdc, 10, 50, Msg, lstrlen(Msg));
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
CloseHandle(hMail);
PostQuitMessage(0);
break;
...
} |
cs |
클라이언트 코드>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 |
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
DWORD cbWritten;
HANDLE hFile;
static int Count = 1;
TCHAR lpMsg[128];
TCHAR* Msg = TEXT("왼쪽 버튼을 누르면 메시지를 보냅니다.");
switch (message)
{
case WM_LBUTTONDOWN:
hFile = CreateFile(TEXT("\\\\.\\mailslot\\Test"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
MessageBox(hWnd, TEXT("메일슬롯을 열 수 없습니다."), TEXT("에러"), MB_OK);
break;
}
wsprintf(lpMsg, TEXT("메일슬롯 테스트 문자열 : #%d"), Count++);
WriteFile(hFile, lpMsg, (lstrlen(lpMsg) + 1) * 2, &cbWritten, NULL);
CloseHandle(hFile);
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다.
TextOut(hdc, 10, 100, Msg, lstrlen(Msg));
EndPaint(hWnd, &ps);
}
break;
...
} |
cs |