네임드 파이프의 실무 ── Windows 프로세스 간 통신의 정석을 설계부터 보안까지

· · Windows, 프로세스 간 통신, Windows 개발, C#, C++, 보안, Win32 API

「상주 서비스와 설정 UI가 명령을 주고받게 하고 싶다」. 「관리자 권한이 필요한 작업만 별도 프로세스로 분리하고 싶다」. 「같은 PC의 도구끼리 데이터를 넘기고 싶다」── Windows에서 이런 프로세스 간 통신(IPC)이 필요해지면, 먼저 검토해야 할 정석이 네임드 파이프입니다.

Windows 프로세스 간 통신 선택 기사에서는 네임드 파이프를 「같은 머신 IPC의 첫 후보」로 두었습니다. 이 글은 그 각론입니다. 왜 첫 후보인지, 모드와 서버 형태를 어떻게 고르는지, 그리고 특권 서비스가 쓸 때 무엇을 지켜야 하는지── Windows에서 업무 앱과 서비스를 쓰는 개발자를 대상으로, 그 설계 판단의 재료를 1차 정보로 정리합니다.

1. 먼저 결론

  • 네임드 파이프는 \\.\pipe\이름 형태의 이름공간을 가진 양방향 프로세스 간 채널입니다. 같은 이름으로 여러 인스턴스를 만들어 여러 클라이언트를 동시에 받을 수 있습니다.1
  • 같은 머신 IPC의 첫 후보가 되는 이유는 보안 모델입니다. ACL로 연결자를 제어할 수 있고, 서버는 클라이언트의 Windows 계정을 확인·대여(위장)할 수 있습니다. localhost TCP에는 둘 다 없습니다.2
  • 「한 번의 쓰기 = 한 메시지」로 다루고 싶다면 메시지 모드, 이미 자체 프레이밍이 있다면 바이트 모드입니다. 메시지 모드에서도 짧은 버퍼에서의 분할 읽기(ERROR_MORE_DATA) 처리는 필요합니다.3
  • 여러 클라이언트는 「여러 인스턴스 + overlapped I/O」또는 「.NET async/await」로 받습니다. 공식 샘플은 단일 스레드로 여러 인스턴스를 처리하는 형태를 보여 줍니다.4
  • 보안의 최소선은 네 가지입니다. 원격 거부(PIPE_REJECT_REMOTE_CLIENTS), ACL을 명시하기, FILE_FLAG_FIRST_PIPE_INSTANCE로 가로채기 검출, 클라이언트 측에서 위장 수준을 최소화하기.56
  • ImpersonateNamedPipeClient에서는 반환값 확인이 생명선입니다. 실패를 무시하면 처리는 서버 권한으로 이어집니다.6

2. 네임드 파이프란 무엇인가 ── 이름공간, 인스턴스, 연결의 흐름

네임드 파이프는 \\.\pipe\MyCompany.MyApp.Control처럼 이름으로 식별되는 채널입니다. 서버는 CreateNamedPipe로 만들고, 클라이언트는 같은 이름을 CreateFile로 엽니다. 열리고 나면 양쪽이 ReadFile / WriteFile로 읽고 씁니다── 파일 I/O와 같은 형태로 쓸 수 있다는 점이 특징입니다.1

중요한 개념은 인스턴스입니다. 같은 이름의 파이프 인스턴스를 여러 개 만들 수 있고, 인스턴스 하나는 클라이언트 하나와의 채널 하나입니다. 첫 CreateNamedPipe 호출이 최대 인스턴스 수(또는 무제한)를 정합니다.3

클라이언트 측 연결에는 정석이 있습니다. 모든 인스턴스가 사용 중이면 CreateFile은 ERROR_PIPE_BUSY로 실패하므로, WaitNamedPipe로 빈자리를 기다린 뒤 다시 시도합니다. 또한 열 때 지정하는 접근은 서버가 만든 방향과 맞아야 합니다── 양방향 파이프는 읽기나 쓰기 중 하나로 열 수 있지만, 서버가 쓰기만 하는 outbound 파이프는 읽기 전용으로, 서버가 읽기만 하는 inbound 파이프는 쓰기 전용으로 열지 않으면 CreateFile이 실패합니다.7

파이프 방향과 클라이언트의 접근 지정양방향 파이프는 읽기나 쓰기로 열 수 있지만, 서버가 쓰기만 하는 outbound는 읽기 전용으로, 서버가 읽기만 하는 inbound는 쓰기 전용으로 열어야 한다양방향OutboundInbound서버가 만든 방향?읽기나 쓰기 모두 가능읽기 전용으로 연다쓰기 전용으로 연다

그림 1: 방향과 접근 지정의 불일치는 CreateFile 실패가 된다. 연결 오류를 조사할 때는 여기를 먼저 본다.

네임드 파이프의 기본 구조서버는 같은 이름의 파이프 인스턴스를 여러 개 만들고 ConnectNamedPipe로 연결을 기다린다. 각 클라이언트는 CreateFile로 그 이름을 열어 인스턴스 하나와 1대1 양방향 채널을 갖는다서버인스턴스 1인스턴스 2인스턴스 3클라이언트 A클라이언트 B클라이언트 C

그림 2: 같은 이름의 인스턴스를 여러 개 두면, 서버 하나가 여러 클라이언트와 동시에 1대1로 대화할 수 있다.

네임드 파이프는 SMB 너머로 원격에서도 열 수 있습니다(\\server\pipe\name). 다만 요즘 설계에서 이를 적극적으로 쓸 이유는 거의 없고, 문제는 오히려 쓰지 않을 때 열어 두지 않는 것입니다(5장).

3. 바이트 모드와 메시지 모드

파이프에는 두 가지 전송 모드가 있습니다.3

  • 바이트 모드(PIPE_TYPE_BYTE): TCP와 같은 「끊김 없는 바이트열」입니다. 한 메시지가 어디서 끝나는지는 직접 정합니다(길이 접두사 같은 프레이밍을 설계합니다).
  • 메시지 모드(PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): 한 번의 쓰기가 한 메시지로 다루어지고, 읽는 쪽은 그 단위로 받습니다. 요청/응답 교환에는 이쪽이 쉽습니다.

메시지 모드에는 편리한 짝인 TransactNamedPipe도 있습니다. 요청을 보내고 응답을 받는 일을 한 번의 호출로 합니다.8 함정도 있습니다. 수신 버퍼가 메시지 전체보다 작으면 읽기는 ERROR_MORE_DATA를 반환하고 분할 읽기가 됩니다. 메시지 모드라고 「한 번의 Read가 항상 전체를 가져온다」고 가정하지 말고, 나머지를 읽는 루프를 써야 합니다. 읽기 모드는 핸들마다의 설정이며, CreateNamedPipe가 정하는 것은 서버 측뿐입니다. 클라이언트는 CreateFile 뒤에 SetNamedPipeHandleState로 지정합니다(.NET에서는 연결 후의 ReadMode).7

메시지 모드의 분할 읽기 루프ReadFile이 성공하면 메시지는 완료이고, ERROR_MORE_DATA면 버퍼에 담기지 않은 나머지를 읽어 이어 붙이며, 그 밖의 오류는 끊김으로 다룬다성공ERROR_MORE_DATA그 밖의 오류ReadFile로 읽기결과?메시지 완료나머지를 읽어 이어 붙이기끊김으로 다룬다

그림 3: 메시지 모드에서도 「나머지 읽기 루프」가 필요하다. 없으면 큰 메시지만 깨진다.

바이트 모드와 메시지 모드의 차이바이트 모드에서는 세 번의 쓰기가 끊김 없는 바이트열이 되어 수신 측이 나눠야 하고, 메시지 모드에서는 각 쓰기의 단위가 보존되어 수신 측에 그대로 도착한다바이트 모드: AAA, BB, CCCC 쓰기바이트열 AAABBCCCC로 수신프레이밍은 직접 설계메시지 모드: 같은 세 번의 쓰기AAA, BB, CCCC 세 메시지로 수신쓰기 단위가 보존된다

그림 4: 메시지 모드는 「쓰기의 단위」를 보존해 전달한다. 프레이밍 설계는 필요 없어지지만, 분할 읽기 처리는 잊지 않는다.

어느 쪽을 고를지의 실무 규칙은 단순합니다. 교환이 「요청과 응답」형태라면 메시지 모드. 이미 프레이밍이 들어 있는 형식(길이 접두 직렬화 데이터나 스트림 전송)을 실어 나른다면 바이트 모드. .NET에서 PipeTransmissionMode.Message를 지정하는 것이 전자에 해당합니다.9

4. 서버 설계 ── 클라이언트마다 스레드인가, Overlapped인가

서버의 기본 동작은 「인스턴스를 만든다 → ConnectNamedPipe로 클라이언트를 기다린다 → 읽고 쓴다 → 끊고 다음 클라이언트로」의 루프입니다. 여러 클라이언트를 동시에 상대하는 형태는 두 가지입니다.

동기형, 인스턴스마다 스레드 하나. 인스턴스마다 스레드를 할당하고, 각각이 자신의 클라이언트와 동기 I/O로 대화합니다. 코드는 단순하지만 클라이언트마다 스레드를 소비하고, 전체를 종료할 때 블로킹 I/O에서 빠져나올 수단도 필요합니다.

Overlapped(비동기). 인스턴스를 FILE_FLAG_OVERLAPPED로 만들고, ConnectNamedPipe / ReadFile / WriteFile을 비동기로 발행한 뒤, 소수의 스레드가 모든 인스턴스의 완료를 받습니다. Microsoft 공식 샘플은 이벤트 배열을 WaitForMultipleObjects로 기다리고 단일 스레드로 여러 인스턴스를 처리하는 서버를 보여 줍니다.4 비동기 I/O의 일반론은 I/O 연재 기사에서 설명한 대로이며, 규모가 커지면 IOCP나 스레드 풀 I/O에 붙일 수도 있습니다.

Overlapped 서버의 구조각 인스턴스의 비동기 작업 완료를 이벤트 배열로 받고, 소수의 스레드가 WaitForMultipleObjects로 기다려 완료된 인스턴스를 진행시켜, 스레드 수를 클라이언트 수에서 분리한다인스턴스 1 비동기 작업이벤트 배열인스턴스 2 비동기 작업인스턴스 3 비동기 작업WaitForMultipleObjects로 완료 대기완료된 인스턴스를 진행

그림 5: Overlapped 형태는 스레드 수를 클라이언트 수에서 분리한다. 공식 샘플은 이 주기를 단일 스레드에서 돌린다.

.NET은 이 선택을 거의 없앱니다. NamedPipeServerStreamWaitForConnectionAsync / ReadAsync / WriteAsyncasync/await와 함께 쓰면, 동기형만큼 단순한 코드로 overlapped의 효율을 얻습니다.9

// C#: 여러 클라이언트를 받는 서버의 골격
while (!token.IsCancellationRequested)
{
    var server = new NamedPipeServerStream(
        "MyCompany.MyApp.Control",
        PipeDirection.InOut,
        NamedPipeServerStream.MaxAllowedServerInstances,
        PipeTransmissionMode.Message,
        PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);

    try
    {
        await server.WaitForConnectionAsync(token);
    }
    catch
    {
        await server.DisposeAsync();        // 연결 전에 빠져나올 때는 직접 dispose
        throw;
    }
    _ = HandleClientAsync(server, token);   // 연결 후 소유권은 핸들러로
}

PipeOptions.CurrentUserOnly는 「같은 사용자의 프로세스에서만 연결을 허용한다」는 지정으로, ACL을 직접 쓰지 않아도 되는 편리하고 안전한 기본값입니다.10 사용자를 넘나드는 구성(서비스 ↔ 사용자 세션의 앱 등)에서는 쓸 수 없으므로, 그때는 다음 장의 ACL 설계로 넘어갑니다.

.NET 비동기 서버의 accept 루프accept 루프는 NamedPipeServerStream을 만들고 WaitForConnectionAsync로 연결을 기다린 뒤, 도착하면 클라이언트 처리를 비동기로 떼어 내고 바로 다음 accept로 돌아가, 동시 연결을 단순한 코드로 처리한다서버 스트림을 만든다WaitForConnectionAsync로 대기연결이 도착한다클라이언트 처리를 비동기로 떼어 낸다

그림 6: accept 루프는 「대기 → 떼어 내기 → 다음」주기에 머물고, 각 클라이언트의 처리는 병렬로 진행된다.

5. 보안 ── 특권 서비스가 파이프를 쓸 때 반드시 할 네 가지

네임드 파이프가 같은 머신 IPC의 첫 후보인 가장 큰 이유는 보안 모델이지만, 그건 올바르게 설정했을 때의 이야기입니다. 특히 「관리자 권한 서비스 + 낮은 권한 UI 앱」이라는 브로커 설계에서는, 파이프 자체가 권한 경계입니다. 고정할 점은 네 가지입니다.

(1) 원격을 거부한다. 로컬 IPC로 쓰려던 파이프가 네트워크에서 열리면, 그 자체로 공격면입니다. CreateNamedPipePIPE_REJECT_REMOTE_CLIENTS를 지정하면 원격 클라이언트 연결은 자동으로 거절됩니다.5

(2) ACL을 명시한다. SECURITY_ATTRIBUTES에 보안 기술자를 넘겨, 연결을 허용할 사용자와 그룹을 좁힙니다. 클라이언트에 GENERIC_WRITE를 주지 마십시오── 그 안에 포함된 FILE_CREATE_PIPE_INSTANCE 권한은, 인가된 클라이언트 자신이 같은 이름의 서버 인스턴스를 만들어 이후 연결을 가로챌 수 있게 합니다. 읽기와 쓰기는 개별 권한으로 주고, 인스턴스 생성 권한은 넘기지 않습니다.11

(3) 이름 가로채기를 막는다. 파이프 이름은 선착순입니다. 악성 프로세스가 같은 이름 파이프를 먼저 만들어 기다리면, 클라이언트는 가짜 서버에 연결합니다. 서버는 첫 인스턴스를 만들 때 FILE_FLAG_FIRST_PIPE_INSTANCE를 지정해 「내가 먼저다」를 보장하고, 그게 실패하면 가로채기를 의심하고 멈춥니다. 이 플래그는 이름을 주장하는 첫 인스턴스 전용이며, 두 번째 이후 인스턴스에 붙이면 생성이 실패합니다.3

(4) 클라이언트는 위장 수준을 필요한 최소로 낮춘다. 상대가 가짜 서버일 경우를 위한 준비입니다. 클라이언트가 CreateFile에 **SECURITY_SQOS_PRESENT SECURITY_IDENTIFICATION을 지정하면, 서버는 클라이언트를 식별할 수 있지만 **그 권한을 빌려 행동할 수는 없습니다.2 다만 위장 워크플로와의 트레이드오프입니다── 서버가 클라이언트 권한으로 실제 접근을 하는 브로커 설계에서는 identification 수준만으로는 위장이 성공하지 않고, SECURITY_IMPERSONATION을 허용해야 합니다. 그 허용은 진짜 서버에 연결했음을 확신할 수 있을 때의 조건입니다. 서버 측 가로채기 대책은 시작 실패로 알아차리는 장치일 뿐이며, 진짜 서비스가 없고 공격자가 같은 이름 파이프를 먼저 만들면 클라이언트는 여전히 가짜 서버에 연결할 수 있습니다. 서비스 기동을 보장하거나 연결 후 상호 인증으로 상대를 확인할 수 있을 때만 허용하십시오.

서버 측의 신원 확인과 권한 대여는 ImpersonateNamedPipeClient입니다. 파이프에서 요청을 읽은 뒤에 호출하면, 호출 스레드는 마지막으로 읽은 메시지의 송신자 보안 문맥에서 움직이기 시작합니다. 클라이언트 권한으로 파일을 열면 접근 검사는 클라이언트에 대해 이루어집니다── 특권 서비스가 「요청된 작업을, 요청자의 권한으로」실행하는 구조입니다.6 쓸 때의 절대 조건은 반환값 확인입니다. 위장에 실패했는데 이어가면 이후 조작은 서버 자신의 높은 권한으로 실행됩니다. 공식 문서는 「실패 시 클라이언트의 요청을 실행해서는 안 된다」고 명시합니다. 작업 후의 RevertToSelf와 함께, 위장 토큰 기사의 실천이 그대로 적용됩니다.

위장을 쓰는 요청 처리의 흐름서버는 파이프에서 요청을 읽고 ImpersonateNamedPipeClient가 성공했음을 확인한 뒤, 클라이언트 권한으로 작업을 수행하고 RevertToSelf로 자신의 문맥으로 돌아온다. 위장이 실패하면 요청을 실행하지 않고 거절한다서버클라이언트서버클라이언트실패 시 실행하지 않고 거절요청을 보낸다요청을 읽는다ImpersonateNamedPipeClient클라이언트 권한으로 작업RevertToSelf로 원래 문맥 복원결과로 응답한다

그림 7: 위장이 성공했음을 확인하는 일과 확실한 RevertToSelf는 한 세트다. 실패 후 이어가면 서버 권한으로 실행된다.

특권 서비스의 파이프를 지키는 네 가지서버 측은 원격 거부, 명시적 ACL, 첫 인스턴스 보장으로 입구를 굳히고, 클라이언트 측은 필요한 최소 위장 수준을 지정해 가짜 서버가 권한을 대여하지 못하게 한다(설계가 서버의 권한 대여를 허용하지 않으면 identification 수준으로 좁힌다)클라이언트 측최소 위장 수준을 지정서버 측PIPE_REJECT_REMOTE_CLIENTSACL로 연결자를 제한FIRST_PIPE_INSTANCE(첫 인스턴스만)권한 경계로서의 파이프

그림 8: 파이프가 권한 경계인 설계에서는, 서버 측 세 가지와 클라이언트 측 한 가지를 세트로 구현한다.

6. 실무의 함정

기동 순서의 경합. 서버가 파이프를 만들기 전에 클라이언트가 연결하러 오면 「파이프가 없다」오류가 납니다. 클라이언트 측에는 「없다 → 잠깐 기다리고 재시도」를 넣습니다. 반대로 서버 측의 원칙은 클라이언트가 시작하기 전에 ConnectNamedPipe로 대기하는 것입니다.8

클라이언트 연결 재시도의 흐름CreateFile로 파이프를 연다. 파이프가 없으면 잠깐 기다리고 재시도하고, 모든 인스턴스가 사용 중(ERROR_PIPE_BUSY)이면 WaitNamedPipe로 빈자리를 기다린 뒤 재시도하며, 성공하면 통신에 들어간다성공파이프가 없음ERROR_PIPE_BUSYCreateFile로 연다결과?통신을 시작한다잠깐 대기(서버 미기동)WaitNamedPipe로 빈 인스턴스 대기

그림 9: 클라이언트 연결 처리는 「없음」과 「가득 참」두 실패를 구분해, 둘 다 재시도로 되돌린다.

끊김 검출. 상대가 종료하면 Read/Write는 ERROR_BROKEN_PIPE 등으로 실패합니다. 그건 이상이 아니라 일상의 통신입니다. 서버는 끊김을 검출하고 인스턴스를 DisconnectNamedPipe한 뒤 다음 연결을 준비하고, 클라이언트는 다시 연결합니다── 절전/재개 기사에서 말한 「멱등한 재연결」의 생각이 여기에도 적용됩니다.

메시지 크기에 대한 가정. 메시지 모드의 분할 읽기(3장)에 더해, 프로토콜의 일부로 「한 메시지의 최대 바이트」를 정하지 않으면, 악의적(또는 버그 있는) 상대가 거대한 메시지로 메모리를 낭비할 수 있습니다. 상한을 정하고, 넘으면 끊는 것이 안전한 접근입니다.

쓰기 완료와 상대의 수신은 다른 일입니다. WriteFile의 성공은 상대 앱이 데이터를 처리했다는 뜻이 아닙니다. 확실성이 필요한 작업은 응답 메시지로 확인하고, 요청과 응답의 대응을 프로토콜에 넣는 설계로 뒷받침합니다.

7. 정리

  • 네임드 파이프는 같은 머신 IPC의 첫 후보입니다. 이유는 파일 I/O와 같은 사용 편의성, 그리고 ACL과 위장이라는 Windows 보안 모델과의 통합입니다.
  • 모드 선택은 「요청과 응답이면 메시지 모드, 이미 자체 프레이밍이 있으면 바이트 모드」입니다. 메시지 모드에서도 분할 읽기(ERROR_MORE_DATA) 처리는 필요합니다.
  • 여러 클라이언트는 여러 인스턴스 + overlapped, 또는 .NET async/await입니다. 신규라면 .NET 비동기형이 단순합니다.
  • 권한 경계인 파이프에서는 원격 거부, 명시적 ACL, FIRST_PIPE_INSTANCE(첫 인스턴스만), 클라이언트 측 위장 수준 최소화를 세트로 가져갑니다.
  • ImpersonateNamedPipeClient에서는 반환값 확인과 RevertToSelf가 생명선입니다.
  • 기동 순서, 끊김, 메시지 상한, 응답 확인이라는 「통신의 일상」을 프로토콜 설계에 짜넣습니다.

네임드 파이프는 오래된 API이지만, 「같은 머신에서 Windows 계정 경계를 지키며 프로세스가 대화하게 한다」는 용도에서는 여전히 가장 자연스러운 도구입니다. 설계 판단의 포인트는 이 글의 범위에서 거의 다 나옵니다. 그다음은 구현을 시작하기 전에 자체 프로토콜을 한 장에 적어 두는 일입니다.

관련 기사

관련 상담 영역

합동회사 코무라소프트에서는 서비스와 UI 앱의 분리, 관리자 권한 격리처럼 프로세스 간 통신이 끼는 설계·구현, 기존 IPC(공유 메모리, 자체 소켓, COM 등)를 네임드 파이프로 바꾸는 작업, 특권 서비스의 파이프 통신 보안 리뷰를 다루고 있습니다. 프로토콜 설계를 함께 보는 단계부터 상담해 주십시오.

참고 링크

  1. Microsoft Learn, Named Pipes. 네임드 파이프가 파이프 서버와 하나 이상의 파이프 클라이언트 사이의 단방향 또는 양방향 채널이라는 점, 모든 인스턴스가 같은 이름을 공유하면서 독립된 버퍼와 핸들을 가진다는 점, 로컬·원격 프로세스에서 사용할 수 있다는 점에 대해.  2

  2. Microsoft Learn, Impersonating a Named Pipe Client. 위장이 서버 스레드를 클라이언트의 권한 안에서 동작하게 한다는 점, 기본 위장 수준이 SecurityImpersonation이라는 점, 클라이언트가 CreateFile 시 SECURITY_SQOS_PRESENT 플래그로 위장 수준을 제어할 수 있다는 점(SECURITY_IDENTIFICATION은 식별만 허용)에 대해.  2

  3. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). 파이프 방향(inbound, outbound, bidirectional), 바이트형과 메시지형(PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) 및 읽기 모드(PIPE_READMODE_MESSAGE), 최대 인스턴스 수(PIPE_UNLIMITED_INSTANCES), FILE_FLAG_OVERLAPPED를 통한 비동기 모드, FILE_FLAG_FIRST_PIPE_INSTANCE를 통한 첫 인스턴스 보장, WaitNamedPipe의 기본 타임아웃에 대해.  2 3 4

  4. Microsoft Learn, Named Pipe Server Using Overlapped I/O. overlapped 작업으로 여러 클라이언트와의 동시 연결을 처리하는 단일 스레드 서버의 공식 샘플에 대해. 각 인스턴스의 OVERLAPPED 구조와 이벤트를 WaitForMultipleObjects로 기다리고 완료된 인스턴스의 상태 기계를 진행하는 형태, GetOverlappedResult로 대기 중 I/O의 완료를 확인하는 점에 대해.  2

  5. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). 원격 클라이언트 모드 두 가지, PIPE_ACCEPT_REMOTE_CLIENTS(원격 연결을 받아 보안 기술자로 검사)와 PIPE_REJECT_REMOTE_CLIENTS(원격 클라이언트 연결을 자동 거절)에 대해.  2

  6. Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). 서버 측 스레드가 파이프에서 마지막으로 읽은 메시지의 클라이언트 보안 문맥에서 위장을 시작한다는 점, 완료 후 RevertToSelf로 돌아간다는 점, 위장 실패 후 이어가면 서버 프로세스 자신의(특권) 문맥에서 실행되므로 반환값을 항상 확인하고 실패 시 클라이언트의 요청을 실행해서는 안 된다는 점에 대해.  2 3

  7. Microsoft Learn, Named Pipe Client. 클라이언트가 CreateFile로 파이프를 연다는 점, 모든 인스턴스가 사용 중이면 ERROR_PIPE_BUSY가 나고 WaitNamedPipe로 빈자리를 기다린다는 점, 연 핸들의 기본이 바이트 읽기·블로킹·non-overlapped이며 SetNamedPipeHandleState로 메시지 읽기 모드로 바꿀 수 있다는 점에 대해.  2

  8. Microsoft Learn, Named Pipe Operations. ReadFileEx / WriteFileEx를 통한 overlapped 작업, PeekNamedPipe를 통한 소비하지 않는 읽기, 메시지형 양방향 파이프에서 TransactNamedPipe가 요청 전송과 응답 수신을 한 호출로 한다는 점, 클라이언트가 시작하기 전의 블로킹 읽기가 경합을 일으킬 수 있다는 점에 대해.  2

  9. Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). NamedPipeServerStream / NamedPipeClientStream으로 연결하고 읽고 쓰는 점, PipeTransmissionMode.Message를 통한 메시지 단위 전송, 비동기 메서드로 여러 클라이언트를 처리하는 점에 대해.  2

  10. Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Asynchronous로 비동기 I/O를 켜는 점, CurrentUserOnly가 같은 사용자(및 같은 상승 수준)의 프로세스와만 연결을 허용할 수 있다는 점에 대해. 

  11. Microsoft Learn, Named Pipe Security and Access Rights. 네임드 파이프 접근 권한의 구성, GENERIC_WRITE가 FILE_CREATE_PIPE_INSTANCE를 포함해 클라이언트에 generic write를 주면 서버 인스턴스 생성도 허용된다는 점, 데이터 읽기와 쓰기는 개별 접근 권한으로 부여한다는 점에 대해. 

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

네임드 파이프와 TCP(localhost 소켓)는 어떻게 가려 써야 하나요?
같은 머신 안의 프로세스 간 통신이라면 네임드 파이프가 첫 후보입니다. 이유는 보안 모델입니다. 파이프는 Windows 보안 기술자(ACL)로 「누가 연결할 수 있는지」를 OS 수준에서 제어할 수 있고, 서버는 ImpersonateNamedPipeClient로 연결 상대의 Windows 계정을 확인·대여할 수 있습니다. localhost TCP 포트는 누구나 연결할 수 있어, 상대가 누구인지를 자체 인증으로 확인해야 하는 점과 대조됩니다. 한편 나중에 원격 통신으로 발전할 가능성이 높거나, 다른 OS의 프로세스와도 대화하거나, gRPC 같은 기존 프로토콜 자산을 재사용하고 싶다면 TCP 쪽이 유리합니다. 이 판단은 Windows 프로세스 간 통신 선택 기사에서도 정리되어 있습니다.
바이트 모드와 메시지 모드 중 어느 쪽을 써야 하나요?
「한 번의 쓰기 = 한 단위의 의미」로 다루고 싶다면 메시지 모드(PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE)가 편리합니다. 수신 측은 송신 측이 쓴 단위로 읽을 수 있어, 경계를 직접 관리할 필요가 없습니다. 바이트 모드는 TCP와 같은 「끊김 없는 바이트열」이며, 길이 접두사 같은 프레이밍을 직접 설계해야 합니다. 이미 프레이밍이 있는 프로토콜(예: 길이가 붙은 직렬화 형식)을 실어 나른다면 바이트 모드로 충분합니다. 주의점은, 메시지 모드에서도 수신 버퍼가 메시지보다 작으면 분할 읽기(ERROR_MORE_DATA)가 일어나므로 그 처리는 필요합니다. 또한 읽기 모드는 핸들마다의 설정이며, CreateNamedPipe가 정하는 것은 서버 측뿐입니다. 클라이언트는 CreateFile 뒤에 SetNamedPipeHandleState로 PIPE_READMODE_MESSAGE를 지정해야 합니다. .NET에서는 서버가 PipeTransmissionMode.Message를 지정하고, 클라이언트는 연결 후 NamedPipeClientStream.ReadMode를 Message로 설정합니다.
여러 클라이언트를 동시에 상대하는 서버는 어떻게 만드나요?
네임드 파이프는 같은 이름으로 여러 인스턴스를 만들 수 있고, 인스턴스 하나가 클라이언트 하나를 담당합니다. 형태는 두 가지입니다. 하나는 클라이언트마다 스레드를 할당하는 동기형으로, 구현은 단순하지만 클라이언트 수만큼 스레드를 소비합니다. 다른 하나는 FILE_FLAG_OVERLAPPED로 비동기 I/O를 쓰고, 소수의 스레드가 모든 인스턴스의 ConnectNamedPipe, ReadFile, WriteFile을 처리하는 형태입니다. Microsoft 공식 샘플에도 단일 스레드로 여러 인스턴스를 처리하는 구현이 나와 있습니다. .NET이라면 NamedPipeServerStream.WaitForConnectionAsync와 async/await로, 비동기형을 동기형만큼 단순하게 쓸 수 있습니다. 특별한 이유가 없다면 신규 구현에는 .NET 비동기형을 권합니다.
네임드 파이프 보안에서 최소한 해야 할 일은 무엇인가요?
네 가지입니다. 첫째, 원격 연결이 필요 없으면 PIPE_REJECT_REMOTE_CLIENTS를 지정해 네트워크 너머의 연결을 명시적으로 거부합니다. 둘째, SECURITY_ATTRIBUTES로 적절한 ACL을 설정하고 연결해도 되는 사용자·그룹을 좁힙니다(기본 ACL은 용도에 따라 너무 느슨합니다). 셋째, 첫 인스턴스를 만들 때 FILE_FLAG_FIRST_PIPE_INSTANCE를 지정해, 같은 이름 파이프를 먼저 만드는 「이름 가로채기」를 검출합니다(두 번째 이후 인스턴스에는 붙이지 않습니다). 넷째, 서버에 신원 확인만 시키고 싶은 클라이언트는 CreateFile에 SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION을 지정해, 가짜 서버가 자신의 권한을 대여(위장)하지 못하게 제한합니다. 서버가 클라이언트 권한으로 실제 접근을 하는 브로커 구성에서는 위장을 허용해야 하므로, 이 제한은 「서버에 권한 대여를 시키는 설계인가」에 따라 가려 씁니다.
ImpersonateNamedPipeClient를 쓸 때 주의점은 있나요?
가장 중요한 것은 반환값 확인입니다. 위장에 실패했는데도 처리를 이어가면, 이후 조작은 서버 프로세스 자신의(대개 높은) 권한으로 실행되어, 클라이언트에게 허용되지 않아야 할 조작이 통과합니다. 공식 문서도 실패 시 클라이언트의 요청을 실행해서는 안 된다고 명시합니다. 또한 위장은 「파이프에서 마지막으로 읽은 메시지」의 문맥에서 이루어지므로, 반드시 무언가를 읽은 뒤에 호출하고, 작업이 끝나면 RevertToSelf로 원래 문맥으로 확실히 돌아가야 합니다. 위장 주변의 구조(토큰, 위장 수준, SeImpersonatePrivilege)는 위장 토큰 기사에서 자세히 다룹니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기