파이프  [Windows System Programming]

다루는 내용

Windows System에서는 로컬 망에서 IPC(Inter Process Communication)를 할 수 있게 파이프를 제공합니다.

1. CreateNamedPipe
2. ConnectNamedPipe
3. 서버 코드 설명
4. 클라이언트 코드 설명

설명에 사용할 코드

두 개의 콘솔 응용 프로그램으로 서버와 클라이언트가 있습니다.

서버는 파이프를 생성하고 클라이언트가 연결하면 메시지를 수신하여 화면에 출력합니다.

클라이언트는 사용자가 입력한 메시지를 파이프를 통해 전송합니다.

서버 코드

//나는 서버
#include <Windows.h>
#include <stdio.h>
#define PNAME	TEXT("\\\\.\\pipe\\test")
void DoIt(HANDLE hPipe)
{	
	char buf[256];
	BOOL check;
	while (1)
	{
		check = ReadFile(hPipe, buf, 256, 0, 0);
		if ((check == FALSE) ||(strcmp(buf, "exit") == 0))
		{
			break;
		}
		printf("%s\n", buf);
	}
	printf("연결을 닫습니다.\n");
	CloseHandle(hPipe);
}
int main()
{
	HANDLE hPipe;
	BOOL check;
	while (1)
	{
		hPipe = CreateNamedPipe(PNAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE,
			1, 4096, 4096, 0, 0);
		check = ConnectNamedPipe(hPipe, 0);
		if ((check == false) && (GetLastError() == ERROR_PIPE_CONNECTED))
		{
			check = true;
		}
		if (check)
		{
			DoIt(hPipe);
		}
	}
	return 0;
}

클라이언트 코드

//나는 클라이언트
#include <Windows.h>
#include <stdio.h>
#include <string.h>
#define PNAME	TEXT("\\\\.\\pipe\\test")
int main()
{
	HANDLE hPipe = CreateFile(PNAME, GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		0, OPEN_EXISTING,0 ,0);
	if (hPipe == INVALID_HANDLE_VALUE)
	{
		printf("연결 실패\n");
		return 0;
	}
	printf("서버에 연결하였습니다.\n");
	while (1)
	{
		printf("메시지(종료:exit):");
		char buf[256];
		scanf_s("%s", buf, 256);
		WriteFile(hPipe, buf, 256, 0, 0);
		printf("전송하였습니다.\n");
		if (strcmp(buf, "exit") == 0)
		{
			break;
		}
	}
	CloseHandle(hPipe);
	return 0;
}

1. CreateNamedPipe

이름 있는 파이프를 생성하는 함수입니다.

HANDLE
WINAPI
CreateNamedPipe(
    LPCWSTR lpName,
    DWORD dwOpenMode,
    DWORD dwPipeMode,
    DWORD nMaxInstances,
    DWORD nOutBufferSize,
    DWORD nInBufferSize,
    DWORD nDefaultTimeOut,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes
    );                                                   msdn 바로가기

반환 값

이름 있는 파이프 instance 핸들을 반환합니다.

실패하면 INVALID_HANDLE_VALUE를 반환합니다.

LPCWSTR lpName

파이프 이름입니다.

파이프 이름은 \\호스트이름\pipe\파이프이름입니다.

로컬 호스트는 .으로 표시합니다.

\\.\pipe\파이프이름

C언어와 C++언어에서 문자열로 나타낼 때는 “\\\\.\\pipe\\파이프이름”처럼 표현해야 합니다.

DWORD dwOpenMode

파이프 열기 모드로 액세스 모드라고 볼 수 있습니다.

수신 목적이면 PIPE_ACCESS_INBOUND를 사용합니다.

송신 목적이면 PIPE_ACCESS_OUTBOUND를 사용합니다.

송수신을 모두 하려면 PIPC_ACCESS_DUPLEX를 사용합니다.

#define PIPE_ACCESS_INBOUND         0x00000001
#define PIPE_ACCESS_OUTBOUND        0x00000002
#define PIPE_ACCESS_DUPLEX          0x00000003

DWORD dwPipeMode

파이프 모드입니다.

바이트 스트림 방식으로 통신하려면 PIPE_TYPE_BYTE를 사용합니다.

메시지 단위로 통신하려면 PIPE_TYPE_MESSAGE를 사용합니다.

#define PIPE_TYPE_BYTE              0x00000000
#define PIPE_TYPE_MESSAGE           0x00000004

DWORD nMaxInstances

허용하는 최대 인스턴스 수입니다.

DWORD nOutBufferSize

송신 버퍼 사이즈입니다.

DWORD nInBufferSize

수신 버퍼 사이즈입니다.

DWORD nDefaultTimeOut

디폴트 대기 시간입니다.

클라이언트에서 파이프에 연결 대기할 때 WaitNamedPipe 함수 호출 시에 두 번째 인자를  NMPWAIT_USE_DEFAULT_WAIT를 줄 때의 디폴트 대기 시간입니다.

LPSECURITY_ATTRIBUTES lpSecurityAttributes

보안 설명자입니다.

NULL을 전달하면 기본 보안 설명자를 사용합니다.

2. ConnectNamedPipe

서버 측에서 클라이언트의 연결을 대기하는 함수입니다.

BOOL
WINAPI
ConnectNamedPipe(
    HANDLE hNamedPipe,
    LPOVERLAPPED lpOverlapped
    );

반환 값

성공하면 0이 아닙니다.

실패하면 0입니다.

만약 CreateNamePipe 함수 호출과 ConnectNamePipe 함수 호출 사이에 클라이언트 요청이 오면 0을 반환합니다. 이 때는 GetLastError 함수 호출의 반환 값이 ERROR_PIPE_CONNECTED입니다.

LPOVERLAPPED lpOverlapped

OVERLAPPED 구조체의 포인터입니다.

비동기 호출에 사용합니다.

*비동기보다 non-blocking이라고 말하는 것이 좀 더 정확한 표현일 수 있습니다.*

3. 서버 코드 설명

서버와 클라이언트에서 공통으로 사용할 파이프 이름을 결정합니다.

#define PNAME	TEXT("\\\\.\\pipe\\test")

DoIt 함수

클라이언트와 연결 상태의 파이프에서 메시지를 수신하는 함수입니다.

파이프의 입출력은 파일 입출력 함수를 사용합니다.

따라서 수신할 때 ReadFile 함수를 사용합니다.

여기에서는 수신한 메시지가 “exit”이 올 때까지 메시지를 수신하고 화면에 출력합니다.

void DoIt(HANDLE hPipe)
{	
	char buf[256];
	BOOL check;
	while (1)
	{
		check = ReadFile(hPipe, buf, 256, 0, 0);
		if ((check == FALSE) ||(strcmp(buf, "exit") == 0))
		{
			break;
		}
		printf("%s\n", buf);
	}
	printf("연결을 닫습니다.\n");
	CloseHandle(hPipe);
}

main 진입점 함수

서버에서는 다음의 작업을 반복합니다.

파이프를 생성합니다.

클라이언트 연결을 대기합니다.

클라이언트 연결 대기가 성공하면 통신을 수행합니다.

int main()
{
	HANDLE hPipe;
	BOOL check;
	while (1)
	{
		hPipe = CreateNamedPipe(PNAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE,
			1, 4096, 4096, 0, 0);
		check = ConnectNamedPipe(hPipe, 0);
		if ((check == false) && (GetLastError() == ERROR_PIPE_CONNECTED))
		{
			check = true;
		}
		if (check)
		{
			DoIt(hPipe);
		}
	}
	return 0;
}

4. 클라이언트 코드 설명

클라이언트에서 파이프에 연결하는 것은 단지 약속한 파이프 이름으로 파일 열기를 하는 것입니다.

연결 성공하면 다음을 반복합니다.

메시지 입력합니다.

메시지를 전송합니다.

입력한 메시지가 “exit”이면 반복문을 탈출합니다.

int main()
{
	HANDLE hPipe = CreateFile(PNAME, GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		0, OPEN_EXISTING,0 ,0);
	if (hPipe == INVALID_HANDLE_VALUE)
	{
		printf("연결 실패\n");
		return 0;
	}
	printf("서버에 연결하였습니다.\n");
	while (1)
	{
		printf("메시지(종료:exit):");
		char buf[256];
		scanf_s("%s", buf, 256);
		WriteFile(hPipe, buf, 256, 0, 0);
		printf("전송하였습니다.\n");
		if (strcmp(buf, "exit") == 0)
		{
			break;
		}
	}
	CloseHandle(hPipe);
	return 0;
}