메뉴 닫기

[WinAPI] 파이프로 구현하는 클라이언트-서버 통신, CreateNamedPipe 완전정복

[WinAPI] 파이프로 구현하는 클라이언트-서버 통신, CreateNamedPipe 완전정복

📌 윈도우에서 안전하고 효율적인 IPC 통신 구현법, 지금 확인해보세요!

윈도우 시스템 프로그래밍을 시작하다 보면 ‘프로세스 간 통신(IPC)’이라는 용어를 자주 접하게 됩니다.
한 프로그램에서 다른 프로그램으로 데이터를 주고받는 이 기능은, 단순히 기술적인 요소를 넘어 소프트웨어 아키텍처를 구성하는 데 매우 핵심적인 역할을 하죠.
특히 Named Pipe는 WinAPI에서 제공하는 강력한 통신 수단으로, 로컬 또는 원격 클라이언트와 서버 간의 안정적인 데이터 스트림을 구성할 수 있도록 도와줍니다.
개념은 어렵지 않지만 직접 구현하려다 보면 막히는 부분이 많아 헤매는 경우가 많은데요.
이번 글에서는 그 고민을 시원하게 풀어드립니다.

이 글에서는 WinAPI 기반 파이프 통신의 기본 개념부터 실제 코드 구현 방법까지 차근차근 짚어봅니다.
CreateNamedPipe, ConnectNamedPipe, ReadFile, WriteFile 등 실제 사용하는 API 함수들을 중심으로 어떤 구조로 통신을 구성하고, 어떻게 안전하게 데이터를 주고받는지 실전 예제와 함께 설명해드릴게요.
WinAPI에 처음 입문하시는 분도 이해할 수 있도록 최대한 쉬운 표현으로 구성했으니, 걱정하지 마시고 따라와 주세요.



🔧 파이프(Named Pipe)란?

윈도우 운영체제에서는 서로 다른 프로세스 간에 데이터를 주고받기 위한 여러 통신 수단을 제공합니다.
그중에서도 Named Pipe는 서버-클라이언트 구조로 동작할 수 있는 스트림 기반 IPC(Inter-Process Communication) 방식입니다.
일반적인 익명 파이프(Anonymous Pipe)와 달리, 이름이 지정되어 있어 별도의 프로세스에서도 접근이 가능하다는 특징이 있습니다.

파이프는 기본적으로 커널 수준에서 관리되는 일종의 가상 파일로 동작합니다.
서버는 먼저 파이프를 생성하고, 클라이언트는 그 이름을 통해 파이프에 연결하여 데이터를 송수신합니다.
이 구조는 동기화, 보안, 다중 연결 처리 등의 관점에서도 매우 유연하게 구성할 수 있기 때문에, 다양한 윈도우 애플리케이션에서 사용되고 있죠.

📌 파이프의 핵심 구조 이해

파이프를 통한 통신은 다음과 같은 흐름으로 이루어집니다.
서버는 CreateNamedPipe 함수를 통해 파이프를 생성하고, ConnectNamedPipe를 통해 클라이언트의 연결을 기다립니다.
클라이언트는 CreateFile 함수를 사용해 서버가 만든 파이프에 연결을 시도합니다.
연결이 완료되면 ReadFileWriteFile을 통해 데이터가 오갑니다.

  • 🧱서버: CreateNamedPipe → ConnectNamedPipe
  • 📲클라이언트: CreateFile → 서버의 파이프에 연결
  • 🔁데이터 통신: ReadFile / WriteFile 함수로 송수신

💎 핵심 포인트:
Named Pipe는 윈도우 기반에서 고성능 IPC를 구현할 수 있는 대표적인 수단이며, 네트워크를 통한 원격 통신까지 지원합니다.

🛠️ CreateNamedPipe로 서버 만들기

Named Pipe 통신을 구현하기 위해 가장 먼저 해야 할 일은 서버 측에서 파이프를 생성하는 것입니다.
이를 위해 사용하는 함수가 바로 CreateNamedPipe입니다.
이 함수는 파이프의 이름, 데이터 전송 방식, 동기 또는 비동기 여부, 클라이언트 수 등 다양한 속성을 설정할 수 있는 윈도우 API입니다.

서버는 이 함수를 호출함으로써 커널 내부에 가상 파이프 객체를 생성하고, 지정된 이름을 통해 클라이언트가 접근할 수 있도록 준비합니다.
이후 ConnectNamedPipe 함수를 사용해 클라이언트가 연결되기를 기다리게 되죠.

📌 CreateNamedPipe 함수 구조

CODE BLOCK
HANDLE CreateNamedPipe(
  LPCTSTR lpName,
  DWORD dwOpenMode,
  DWORD dwPipeMode,
  DWORD nMaxInstances,
  DWORD nOutBufferSize,
  DWORD nInBufferSize,
  DWORD nDefaultTimeOut,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

이 함수는 총 8개의 인자를 받습니다.
그중에서도 자주 사용하는 값은 다음과 같습니다:

  • 📛lpName: 파이프 경로 이름 (예: \\.\pipe\mypipe)
  • 🔓dwOpenMode: 읽기/쓰기 모드 및 비동기 설정
  • 🔁dwPipeMode: 메시지 또는 바이트 스트림 여부

💎 핵심 포인트:
파이프 이름은 반드시 \\.\pipe\파이프이름 형식으로 지정해야 하며, 클라이언트는 동일한 이름으로 연결 요청을 보내야 합니다.



🔌 ConnectNamedPipe로 클라이언트 연결

서버가 CreateNamedPipe로 파이프를 생성한 이후, 클라이언트가 이 파이프에 연결할 수 있도록 기다리는 작업이 필요합니다.
이를 담당하는 함수가 바로 ConnectNamedPipe입니다.
이 함수는 생성된 파이프 핸들을 기반으로 클라이언트의 연결 요청을 대기하며, 연결이 성립되면 통신을 시작할 수 있는 상태로 전환됩니다.

만약 클라이언트가 이미 연결된 상태에서 ConnectNamedPipe를 호출하면 실패하게 됩니다.
이러한 상황에 대비해 함수 반환값과 GetLastError 값을 반드시 확인해야 안정적인 처리가 가능합니다.

📌 ConnectNamedPipe 함수 사용 예시

CODE BLOCK
BOOL fConnected = ConnectNamedPipe(hPipe, NULL) ? 
                  TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

위 코드에서는 클라이언트가 먼저 연결되었을 가능성도 고려하여, GetLastError()ERROR_PIPE_CONNECTED를 검사합니다.
이렇게 하면 불필요한 예외 상황 없이 연결을 수락할 수 있습니다.

  • 클라이언트 연결을 ConnectNamedPipe로 대기
  • 🧾반환값이 FALSE일 경우 GetLastError로 오류 확인
  • 이미 연결된 경우 ERROR_PIPE_CONNECTED 코드로 판별

💎 핵심 포인트:
ConnectNamedPipe 호출 전후로 발생할 수 있는 연결 상태를 철저히 예외 처리하는 것이 안정적인 통신 구현의 핵심입니다.

📤 데이터 송수신과 ReadFile, WriteFile

서버와 클라이언트 간 연결이 완료되면 이제 본격적으로 데이터를 주고받을 수 있습니다.
이때 사용하는 함수가 바로 ReadFileWriteFile입니다.
이 두 함수는 이름 그대로 파이프를 통해 데이터를 읽고 쓰는 역할을 하며, 윈도우 파일 입출력 함수들과 동일한 방식으로 작동합니다.

중요한 점은 파이프의 모드에 따라 데이터가 메시지 기반인지 바이트 기반인지에 따라 처리 방식이 달라진다는 점입니다.
메시지 기반 파이프에서는 데이터 단위 구분이 유지되지만, 바이트 기반에서는 스트림처럼 흘러가기 때문에 메시지 경계 처리를 별도로 구현해야 합니다.

📌 WriteFile로 데이터 전송

CODE BLOCK
char buffer[] = "Hello from server!";
DWORD bytesWritten;
WriteFile(hPipe, buffer, strlen(buffer), &bytesWritten, NULL);

WriteFile 함수는 버퍼에 담긴 데이터를 파이프를 통해 전송합니다.
네 번째 인자인 bytesWritten은 실제 전송된 바이트 수를 반환받는 데 사용됩니다.

📌 ReadFile로 데이터 수신

CODE BLOCK
char buffer[128] = {0};
DWORD bytesRead;
ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL);

ReadFile 함수는 파이프에 수신된 데이터를 읽어들입니다.
읽은 결과는 buffer에 저장되며, bytesRead를 통해 실제 수신된 크기를 확인할 수 있습니다.

💎 핵심 포인트:
비동기 모드(OVERLAPPED)로 파이프를 생성한 경우, ReadFile과 WriteFile에도 반드시 OVERLAPPED 구조체를 적용해야 합니다.



💡 실전 예제 코드로 구조 완성하기

지금까지 Named Pipe를 구성하는 핵심 함수들과 구조를 살펴봤습니다.
이제는 그 모든 요소들을 조합해 하나의 클라이언트-서버 통신 예제를 완성해보겠습니다.
아래 코드는 윈도우 콘솔 환경에서 동작하는 간단한 구조로, 기본 개념을 익히기에 가장 적합합니다.

📌 서버 코드 예제

CODE BLOCK
HANDLE hPipe = CreateNamedPipe(
    TEXT("\\\\.\\pipe\\mypipe"),
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
    1, 1024, 1024, 0, NULL);

ConnectNamedPipe(hPipe, NULL);

char buffer[128] = {0};
DWORD bytesRead;
ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL);

printf("Client says: %s\\n", buffer);

const char* reply = "Hello from server!";
DWORD bytesWritten;
WriteFile(hPipe, reply, strlen(reply), &bytesWritten, NULL);

CloseHandle(hPipe);

이 서버 코드는 클라이언트가 보내는 메시지를 받고, 응답을 다시 전송하는 구조입니다.
파이프는 바이트 모드로 설정되어 있으며, 동기 방식으로 작동합니다.

📌 클라이언트 코드 예제

CODE BLOCK
HANDLE hPipe = CreateFile(
    TEXT("\\\\.\\pipe\\mypipe"),
    GENERIC_READ | GENERIC_WRITE,
    0, NULL, OPEN_EXISTING, 0, NULL);

const char* msg = "Hello from client!";
DWORD bytesWritten;
WriteFile(hPipe, msg, strlen(msg), &bytesWritten, NULL);

char buffer[128] = {0};
DWORD bytesRead;
ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL);

printf("Server says: %s\\n", buffer);

CloseHandle(hPipe);

클라이언트는 서버가 만든 파이프에 연결한 후, 메시지를 전송하고 응답을 받습니다.
여기서 중요한 포인트는 서버가 먼저 실행되어 파이프를 준비해놓아야 한다는 점입니다.

💎 핵심 포인트:
실전 코드를 구현할 때는 예외 처리, 비동기 설정, 멀티 클라이언트 대응 등을 함께 고려하여 확장성 있는 구조로 발전시키는 것이 좋습니다.

자주 묻는 질문 (FAQ)

Named Pipe는 어느 윈도우 버전부터 사용할 수 있나요?
Named Pipe는 Windows NT 계열부터 정식으로 지원되었으며, 현재는 모든 Windows 운영체제(Windows 10, 11 포함)에서 사용 가능합니다.
익명 파이프와 Named Pipe의 차이점은 무엇인가요?
익명 파이프는 부모-자식 프로세스 간 단방향 통신에 적합하며, 이름 없이 핸들로만 접근합니다.
반면 Named Pipe는 고유 이름을 갖고 있어 네트워크 또는 별도 프로세스 간 양방향 통신이 가능합니다.
Named Pipe는 로컬 통신에만 사용할 수 있나요?
아닙니다.
Named Pipe는 로컬뿐만 아니라 네트워크 상의 다른 컴퓨터와도 통신할 수 있습니다.
단, 보안 정책과 방화벽 설정에 따라 제약이 있을 수 있습니다.
Named Pipe는 동시 접속 클라이언트를 처리할 수 있나요?
가능합니다.
서버는 여러 개의 인스턴스를 생성하여 다수의 클라이언트를 처리할 수 있으며, 이때는 다중 쓰레드 기반으로 처리하는 것이 일반적입니다.
비동기(Overlapped) 방식으로도 사용할 수 있나요?
네, CreateNamedPipe를 생성할 때 PIPE_FLAG_OVERLAPPED 옵션을 설정하면 비동기 방식으로 구현할 수 있으며, I/O 작업은 OVERLAPPED 구조체로 제어해야 합니다.
보안을 위해 권한 제어가 가능한가요?
가능합니다.
SECURITY_ATTRIBUTES 구조체를 통해 접근 권한을 설정할 수 있으며, 운영체제의 ACL 정책과도 연동됩니다.
파이프 연결 실패 시 복구 방법은 무엇인가요?
연결 실패 시에는 반드시 GetLastError를 확인하고, 필요에 따라 파이프를 다시 생성하거나 일정 시간 후 재시도를 수행하는 방식으로 복구하는 것이 좋습니다.
Named Pipe를 대체할 수 있는 IPC 방식에는 무엇이 있나요?
메모리 맵 파일, 소켓(Socket), 메시지 큐(Message Queue), RPC(Remote Procedure Call) 등이 있으며, 시스템 요구사항과 성능 기준에 따라 선택이 달라집니다.

🧩 WinAPI Named Pipe로 IPC 통신 완성하기

이번 글에서는 윈도우 환경에서 프로세스 간 통신(IPC)을 구현할 수 있는 강력한 도구인 Named Pipe에 대해 알아보았습니다.
CreateNamedPipe로 서버를 구성하고, ConnectNamedPipe로 클라이언트를 연결하며, ReadFile과 WriteFile로 실제 데이터 송수신을 처리하는 전체 구조를 실전 코드와 함께 확인했죠.
이러한 구조는 단순한 테스트 수준을 넘어 실무 애플리케이션에서도 활용할 수 있는 확장성과 안정성을 갖추고 있습니다.

IPC 기술은 초보자에게 다소 어렵게 느껴질 수 있지만, 흐름을 이해하고 하나씩 구현해보면 그리 복잡하지 않습니다.
특히 WinAPI의 파이프 통신은 보안 설정, 다중 클라이언트 처리, 비동기 구현까지 모두 지원하므로 제대로 활용하면 매우 유용한 통신 방식이 됩니다.
코드를 직접 실행해보면서 동작을 확인하고, 나만의 IPC 구조로 발전시켜보시길 추천드립니다.


🏷️ 관련 태그 : WinAPI, NamedPipe, IPC통신, 클라이언트서버구조, 윈도우개발, 시스템프로그래밍, 파이프통신, 윈도우API, 비동기통신, C언어