LISTORY
[윈도우 시스템즈 프로그래밍] 파이프 방식의 IPC 본문
뇌를 자극하는 윈도우즈 시스템 프로그래밍 유투브 강의를 정리한 내용이다.
오늘 정리할 내용은 파이프 방식의 IPC 부분이다.
⊙ YouTube 강의 주소 ⊙ ☞ 파이프 방식의 IPC
파이프 방식의 IPC
|
메일 슬롯 |
이름없는 파이프 |
이름 있는 파이프 |
방향성 |
단방향, 브로드 캐스팅 |
단방향 |
양방향 |
통신 범위 |
제한 없음 |
부모 자식 프로세스 |
제한 없음 |
지난 시간에, 메일 슬롯은 단방향이라 배웠다.
메일 슬롯을 양방향으로 사용하기 위해선 두개의 메일 슬롯이 있어야 한다.
메일슬롯은 리시버를 여러 개 두어 SENDER가 각각의 RECEIVER에게 전송이 가능하다.(브로드 캐스팅)
하지만 브로드캐스팅을 사용하기 위해 메일 슬롯을 쓰는 경우는 잘 없고, 주로 소켓 통신을 통해 이용한다.
여기서 말하는 통신 범위란 무엇일까? 이는 동일한 컴퓨터 상에서만 통신이 가능한가, 또는 네트워크만 연결되어 있다면
다른 컴퓨터에서도 통신이 가능한가 여부를 뜻한다.
메일 슬롯은 가능하다. 다른 컴퓨터라도 주소를 가지고 통신을 하기 때문이다.
이번 시간에 배울 이름 없는 파이프와 이름있는 파이는 이름 여부에 따라 다르다.
여기서 이름은 주소를 의미하는데, 네트워크 상에서 연결되어 있는 pc를 구분할 수 있는 주소이다.
즉, 내 컴퓨터를 구분하기 위한 이름이며, 네트워크 상에서 이름이 없다는 것은 네트워크를 통해 통신할 수 없다는 것을 뜻한다.
한편, 이름 없는 파이프는 메일 슬롯과 동일한 단방향성을 갖는다.
메일 슬롯의 단점을 보완하는 것이 이름있는 파이프이다.
그러면 각각의 파이프에 대해 구체적으로 설명하겠다.
⊙ 이름없는 파이프
예제를 통해 보겠다.
int _tmain(int argc, TCHAR *argv[])
{
HANDLE hReadPipe, hWritePipe //pipe handle
/* pipe 생성*/
CreatePipe(&hReadPipe, &hWritePipe, NULL, 0);
/*pipe의 한쪾 끝을 이용한 데이터 송신*/
WriteFile(
hWritePipe, sendString,
lstrlen(sendString)*sizeof(TCHAR), &bytesWritten, NULL);
_tprintf(_T("string send:%s \n"), sendString);
/*pipe의 다른 한쪽 끝을 이용한 데이터 수신*/
ReadFile(
hReadPipe, recvString,
bytesWritten, &bytesRead, NULL);
...
}
파이프를 가지고 있다고 생각해보자.
파이프는 양쪽이 뚫려 있다. 즉, 입구와 출구가 존재한다.
파이프를 소유하고 있는 내가 파이프의 입구와 출구를 다 소유하고 있는 것이다.
CreatePipe() 함수를 호출 시, wirte가 입구 read가 출구가 된다.
즉, 파이프의 입구, 출구를 사용하여 통신이 가능하다.
다시 위의 표를 참조해 보자.
부모 자식 프로세스간에는 통신하는데 있어 이름 없는 파이프를 쓸 수 있다.
이것이 가능한 이유는 무엇일까?
핸들정보는 상속이 가능하기 때문이다.
상속한 핸들로 파이프를 생성 시, 부모 프로세스와 자식 프로세스 모두 핸들 접속 가능
자식 프로세스를 생성했을 때, 핸들을 상속했을 경우 이름없는 파이프로 통신이 가능하다.
⊙ 이름 있는 파이프
server와 client가 존재한다.
이름 있는 파이프의 경우, 양방향 통신이 가능한데, 여기서 Server가 파이프를 생성한다.
이름있는 파이프의 경우, server가 파이프를 생성한다.
server pipe |
파이프는 CreateNamedPipe()에 의해 생성한다. 이때 파이프는 그냥 Server가 가지고있는 것에 지나지 않는다.
파이프를 사용하기 위해서는, 사용할 수 있도록 밖으로 제공해야 한다.
이를 위해 ConnectNamedPipe() 함수를 호출하여 연결 대기 상태로 전환한다.
그럼 pipe를 오픈한 상태가 된다.
server pipe → (open) |
그렇다면 노출되어 있는 파이프에 연결을 시도하는 것이 client 이다.
client는 CreateFile() 함수를 호출하여 파이프를 연결한다.
이는 메일 슬롯과 같은 의미를 지닌다.
추가적 설명을 위해 예제를 보겠다.
예제
// 서버 코드
#define BUF_SIZE 1024
int CommToClient(HANDLE);
int _tmain(int argc, TCHAR* argv[])
{
LPTSTR pipeName = _T("\\\\.\\pipe\\simple_pipe");
HANDLE hPipe;
while(1)
{
hPipe = CreateNamedPipe (
pipeName, // 파이프 이름
PIPE_ACCESS_CUPLEX, // 읽기, 쓰기 모드 지정
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, // 최대 인스턴스 개수
BUF_SIZE, // 출력 버퍼 사이즈
BUF_SIZE, // 입력 버퍼 사이즈
20000, // 클라이언트 타임-아웃
NULL // 디폴트 보안 속성
);
if ( hPipe == INVALED_HANDLE_VALUE)
{
_tprintf(_T("CreatePipeFailed"));
return -1;
}
BOOL isSuccess;
isSuccess = connectNamedPipe(hPipe, NULL) ? TRUE : FALSE;
if(isSuccess)
CommToClient(hPipe);
else
CloseHandle(hPipe);
return 1;
}
int CommToClient(HANDLE hPipe)
{
TCHAR fileName[MAX_PATH];
TCHAR dataBuf[BUF_SIZE];
BOOL IsSuccess;
DWORD fileNameSize;
isSuccess = ReadFile (
hPipe // 파이프 핸들
fileName, // read 버퍼 지겅
MAX_PATH * sizeof(TCHAR). // READ 버퍼 사이즈
&fileNameSize, // 수신한 데이터 크기
NULL);
IF(!isSuccess || fileNameSize == 0)
{
_tprintf(_T("pipe read message error"));
return -1;
}
FILE *filePtr = _tfopen(fileName, _T("r"));
if(filePtr == NULL)
{
return -1;
}
DWORD bytesWritten = 0;
DWORD bytesRead = 0;
while(!feof(filePtr))
{
bytesRead = fread(dataBuf, 1, BUF_SIZE, filePtr);
WriteFile(
hPipe, // 파이프 핸들
dataBuf, // 전송할 데이터 버퍼
bytesRead, // 전송할 데이터 크기
&bytesWritten, // 전송된 데이터 크기
NULL
);
if(bytesRead != bytesWritten)
{
//error
break;
}
}
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
return 1;
}
코드는 길지만 내용을 보면 하는 일은 단순하다.
이 코드에서 주목해야 할 부분이 두개가 존재한다.
1. 왜 While 안에 있나
동시 접속은 안되지만 여러 프로세스가 순차적으로 접속 가능하기 때문이다.
2. 최대 인스턴스 개수는 무엇인가?
최대 생성 가능한 파이프의 개수이다.
이는 현재 내가 보유할 수 있는 파이프의 개수를 뜻한다.
만일, 보유한 파이프가 소멸되면 다시 파이프를 만들 수 있다.
근데 CreateNamedPipe()는 파이프 생성하는 부분인데 어째서 파이프 개수를 여기서 지정하는 것일까?
이 전달 인자는 CreateNamedPipe() 함수가 처음 호출될 때만 의미를 갖는다..
여기서 인자를 5로 주면 5로 고정되는 것이다. 두번째 호출부터는 바뀌지 않는다.
그래서 두번째 인자부터는 무슨 수를 주어도 상관없지만, MS사에서는 픽스 시켜 똑같이 넣어주는 것을 요구한다.
또한 이 인자는 파이프 이름 별 최대 인스턴스 개수이다.
이 이름에 해당하는 파이프의 인수턴스 개수 지정한다는 뜻이다.
한편, 클라이언트 타임 아웃인자는 무엇을 뜻할까?
클라이언트가 연결 요청을 했을 때, 현재 이 이름으로 생성된 파이프의 인스턴수 개수가 꽉찼다고 가정해보자.
그럼 클라이언트가 기다려야하는데 이 시간을 서버가 지정한다.
물론 클라이언트가 더 기다리도록 클라이언트에서 설정할 수 있다.
// 클라이언트 코드
int _tmain(int argc, TCHAR *argv[])
{
HANDLE hPipe;
TCHAR readDataBuf[BUF_SIZE + 1];
LPTSTR pipeName = _T("\\\\.\\pipe\\simple_pipe");
while(1)
{
hPipe = CreateFile(
pipeName // 파이프 이름
GENERIC_READ | GENERIC_WRITE, // 읽기 쓰기 모드 동시 지정
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
if(hPipe != INVALID_HANDLE_VALUE)
break;
if(GetLastError() != ERROR_PIPE_BUSY)
{
// Could not open pipe
return 0;
}
// 파이프가 꽉 차있을 경우, 더 기다리겠다...
if(!WaitNamedPipe(pipeName, 20000));
{
// Could not open pipe
return 0;
}
}
DWORD pipeMode = PIPE_READMODE_MESSAGEPIPE_WAIT; // 메세지 기반으로 모드 변경
BOOL isSuccess = SetNamedPipeHandleState (
hPipe, // 파이프 핸들
&pipeMode, // 변경할 모드 정보,
NULL,
NULL);
if( !isSuccess)
{
return 0;
}
LPCTSTR fileName = _T("name txt");
DWORD bytesWritten = 0;
}
파이프 생성를 생성했을 때, 데이터를 메세지 기반으로 받을 지, 바이너리 기반으로 받을지 결졍해야한다.
SetNamedPipeHandleStage() 함수로 그것을 결정한다.
기본적으로 데이터를 주고받는 방식을 바이너리라 가정해보자.
어떻게 보내던 간에 받는 이가 메세지 타입으로 받는 이가 메세지타입으로 받으면 그것은 메세지 타입으로 받는것이 된다.
즉, 보내는 이와 받는 이가 방식을 동일하게 지정할 필요가 없다.
서버와 클라이언트의 설정은 별개라는 뜻이다.
하지만 메세지 모드로 통일하여 쓰는 경우가 많다.
'IT > 윈도우 프로그래밍' 카테고리의 다른 글
[윈도우즈 시스템 프로그래밍] 스케줄링 알고리즘과 우선순위 (0) | 2018.07.07 |
---|---|
[윈도우즈 시스템 프로그래밍] 프로세스간 통신(IPC) (0) | 2018.07.01 |
[윈도우즈 시스템 프로그래밍] 프로세스 환경변수 (0) | 2018.06.26 |
[윈도우즈 시스템 프로그래밍] Signaled vs Non-Signaled (0) | 2018.06.17 |
[윈도우 시스템즈 프로그래밍] 프로세스간 통신(IPC) (0) | 2018.06.17 |