「상주 서비스와 설정 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
flowchart TB
accTitle: 파이프 방향과 클라이언트의 접근 지정
accDescr: 양방향 파이프는 읽기나 쓰기로 열 수 있지만, 서버가 쓰기만 하는 outbound는 읽기 전용으로, 서버가 읽기만 하는 inbound는 쓰기 전용으로 열어야 한다
q{"서버가 만든 방향?"} -->|"양방향"| dc["읽기나 쓰기 모두 가능"]
q -->|"Outbound"| oc["읽기 전용으로 연다"]
q -->|"Inbound"| ic["쓰기 전용으로 연다"]
그림 1: 방향과 접근 지정의 불일치는 CreateFile 실패가 된다. 연결 오류를 조사할 때는 여기를 먼저 본다.
flowchart TB
accTitle: 네임드 파이프의 기본 구조
accDescr: 서버는 같은 이름의 파이프 인스턴스를 여러 개 만들고 ConnectNamedPipe로 연결을 기다린다. 각 클라이언트는 CreateFile로 그 이름을 열어 인스턴스 하나와 1대1 양방향 채널을 갖는다
s["서버"] --> i1["인스턴스 1"]
s --> i2["인스턴스 2"]
s --> i3["인스턴스 3"]
c1["클라이언트 A"] <--> i1
c2["클라이언트 B"] <--> i2
c3["클라이언트 C"] <--> i3
그림 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
flowchart TB
accTitle: 메시지 모드의 분할 읽기 루프
accDescr: ReadFile이 성공하면 메시지는 완료이고, ERROR_MORE_DATA면 버퍼에 담기지 않은 나머지를 읽어 이어 붙이며, 그 밖의 오류는 끊김으로 다룬다
read["ReadFile로 읽기"] --> r{"결과?"}
r -->|"성공"| done["메시지 완료"]
r -->|"ERROR_MORE_DATA"| more["나머지를 읽어 이어 붙이기"]
more --> read
r -->|"그 밖의 오류"| dis["끊김으로 다룬다"]
그림 3: 메시지 모드에서도 「나머지 읽기 루프」가 필요하다. 없으면 큰 메시지만 깨진다.
flowchart TB
accTitle: 바이트 모드와 메시지 모드의 차이
accDescr: 바이트 모드에서는 세 번의 쓰기가 끊김 없는 바이트열이 되어 수신 측이 나눠야 하고, 메시지 모드에서는 각 쓰기의 단위가 보존되어 수신 측에 그대로 도착한다
bw["바이트 모드: AAA, BB, CCCC 쓰기"] --> br["바이트열 AAABBCCCC로 수신"]
br --> bf["프레이밍은 직접 설계"]
mw["메시지 모드: 같은 세 번의 쓰기"] --> mr["AAA, BB, CCCC 세 메시지로 수신"]
mr --> mf["쓰기 단위가 보존된다"]
그림 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에 붙일 수도 있습니다.
flowchart TB
accTitle: Overlapped 서버의 구조
accDescr: 각 인스턴스의 비동기 작업 완료를 이벤트 배열로 받고, 소수의 스레드가 WaitForMultipleObjects로 기다려 완료된 인스턴스를 진행시켜, 스레드 수를 클라이언트 수에서 분리한다
i1["인스턴스 1 비동기 작업"] --> ev["이벤트 배열"]
i2["인스턴스 2 비동기 작업"] --> ev
i3["인스턴스 3 비동기 작업"] --> ev
ev --> wait["WaitForMultipleObjects로 완료 대기"]
wait --> proc["완료된 인스턴스를 진행"]
proc --> wait
그림 5: Overlapped 형태는 스레드 수를 클라이언트 수에서 분리한다. 공식 샘플은 이 주기를 단일 스레드에서 돌린다.
.NET은 이 선택을 거의 없앱니다. NamedPipeServerStream의 WaitForConnectionAsync / ReadAsync / WriteAsync를 async/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 설계로 넘어갑니다.
flowchart TB
accTitle: .NET 비동기 서버의 accept 루프
accDescr: accept 루프는 NamedPipeServerStream을 만들고 WaitForConnectionAsync로 연결을 기다린 뒤, 도착하면 클라이언트 처리를 비동기로 떼어 내고 바로 다음 accept로 돌아가, 동시 연결을 단순한 코드로 처리한다
mk["서버 스트림을 만든다"] --> wc["WaitForConnectionAsync로 대기"]
wc --> got["연결이 도착한다"]
got --> hd["클라이언트 처리를 비동기로 떼어 낸다"]
hd --> mk
그림 6: accept 루프는 「대기 → 떼어 내기 → 다음」주기에 머물고, 각 클라이언트의 처리는 병렬로 진행된다.
5. 보안 ── 특권 서비스가 파이프를 쓸 때 반드시 할 네 가지
네임드 파이프가 같은 머신 IPC의 첫 후보인 가장 큰 이유는 보안 모델이지만, 그건 올바르게 설정했을 때의 이야기입니다. 특히 「관리자 권한 서비스 + 낮은 권한 UI 앱」이라는 브로커 설계에서는, 파이프 자체가 권한 경계입니다. 고정할 점은 네 가지입니다.
(1) 원격을 거부한다. 로컬 IPC로 쓰려던 파이프가 네트워크에서 열리면, 그 자체로 공격면입니다. CreateNamedPipe에 PIPE_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와 함께, 위장 토큰 기사의 실천이 그대로 적용됩니다.
sequenceDiagram
accTitle: 위장을 쓰는 요청 처리의 흐름
accDescr: 서버는 파이프에서 요청을 읽고 ImpersonateNamedPipeClient가 성공했음을 확인한 뒤, 클라이언트 권한으로 작업을 수행하고 RevertToSelf로 자신의 문맥으로 돌아온다. 위장이 실패하면 요청을 실행하지 않고 거절한다
participant C as 클라이언트
participant S as 서버
C->>S: 요청을 보낸다
S->>S: 요청을 읽는다
S->>S: ImpersonateNamedPipeClient
Note over S: 실패 시 실행하지 않고 거절
S->>S: 클라이언트 권한으로 작업
S->>S: RevertToSelf로 원래 문맥 복원
S->>C: 결과로 응답한다
그림 7: 위장이 성공했음을 확인하는 일과 확실한 RevertToSelf는 한 세트다. 실패 후 이어가면 서버 권한으로 실행된다.
flowchart TB
accTitle: 특권 서비스의 파이프를 지키는 네 가지
accDescr: 서버 측은 원격 거부, 명시적 ACL, 첫 인스턴스 보장으로 입구를 굳히고, 클라이언트 측은 필요한 최소 위장 수준을 지정해 가짜 서버가 권한을 대여하지 못하게 한다(설계가 서버의 권한 대여를 허용하지 않으면 identification 수준으로 좁힌다)
subgraph sv["서버 측"]
r1["PIPE_REJECT_REMOTE_CLIENTS"]
r2["ACL로 연결자를 제한"]
r3["FIRST_PIPE_INSTANCE(첫 인스턴스만)"]
end
subgraph cl["클라이언트 측"]
r4["최소 위장 수준을 지정"]
end
sv --> safe["권한 경계로서의 파이프"]
cl --> safe
그림 8: 파이프가 권한 경계인 설계에서는, 서버 측 세 가지와 클라이언트 측 한 가지를 세트로 구현한다.
6. 실무의 함정
기동 순서의 경합. 서버가 파이프를 만들기 전에 클라이언트가 연결하러 오면 「파이프가 없다」오류가 납니다. 클라이언트 측에는 「없다 → 잠깐 기다리고 재시도」를 넣습니다. 반대로 서버 측의 원칙은 클라이언트가 시작하기 전에 ConnectNamedPipe로 대기하는 것입니다.8
flowchart TB
accTitle: 클라이언트 연결 재시도의 흐름
accDescr: CreateFile로 파이프를 연다. 파이프가 없으면 잠깐 기다리고 재시도하고, 모든 인스턴스가 사용 중(ERROR_PIPE_BUSY)이면 WaitNamedPipe로 빈자리를 기다린 뒤 재시도하며, 성공하면 통신에 들어간다
cf["CreateFile로 연다"] --> ok{"결과?"}
ok -->|"성공"| go["통신을 시작한다"]
ok -->|"파이프가 없음"| wait1["잠깐 대기(서버 미기동)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["WaitNamedPipe로 빈 인스턴스 대기"]
wait1 --> cf
wnp --> cf
그림 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 계정 경계를 지키며 프로세스가 대화하게 한다」는 용도에서는 여전히 가장 자연스러운 도구입니다. 설계 판단의 포인트는 이 글의 범위에서 거의 다 나옵니다. 그다음은 구현을 시작하기 전에 자체 프로토콜을 한 장에 적어 두는 일입니다.
관련 기사
- Windows의 프로세스 간 통신을 어떻게 선택할까 ── 네임드 파이프 / TCP / gRPC / 공유 메모리 / COM 판단표
- Windows 앱에서 「관리자 권한이 필요한 처리만」을 분리하는 구체적인 방법
- Windows 위장 토큰을 올바르게 다루기 - 스레드 단위 권한 대여와 안전한 복원 방법
- Windows I/O의 심층(제2회) ── 동기 I/O와 비동기 I/O: OVERLAPPED의 진짜 의미
- 공유 메모리를 사용할 때의 함정과 베스트 프랙티스 - 동기, 가시성, 수명, ABI, 보안을 먼저 정리
관련 상담 영역
합동회사 코무라소프트에서는 서비스와 UI 앱의 분리, 관리자 권한 격리처럼 프로세스 간 통신이 끼는 설계·구현, 기존 IPC(공유 메모리, 자체 소켓, COM 등)를 네임드 파이프로 바꾸는 작업, 특권 서비스의 파이프 통신 보안 리뷰를 다루고 있습니다. 프로토콜 설계를 함께 보는 단계부터 상담해 주십시오.
참고 링크
-
Microsoft Learn, Named Pipes. 네임드 파이프가 파이프 서버와 하나 이상의 파이프 클라이언트 사이의 단방향 또는 양방향 채널이라는 점, 모든 인스턴스가 같은 이름을 공유하면서 독립된 버퍼와 핸들을 가진다는 점, 로컬·원격 프로세스에서 사용할 수 있다는 점에 대해. ↩ ↩2
-
Microsoft Learn, Impersonating a Named Pipe Client. 위장이 서버 스레드를 클라이언트의 권한 안에서 동작하게 한다는 점, 기본 위장 수준이 SecurityImpersonation이라는 점, 클라이언트가 CreateFile 시 SECURITY_SQOS_PRESENT 플래그로 위장 수준을 제어할 수 있다는 점(SECURITY_IDENTIFICATION은 식별만 허용)에 대해. ↩ ↩2
-
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
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. overlapped 작업으로 여러 클라이언트와의 동시 연결을 처리하는 단일 스레드 서버의 공식 샘플에 대해. 각 인스턴스의 OVERLAPPED 구조와 이벤트를 WaitForMultipleObjects로 기다리고 완료된 인스턴스의 상태 기계를 진행하는 형태, GetOverlappedResult로 대기 중 I/O의 완료를 확인하는 점에 대해. ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). 원격 클라이언트 모드 두 가지, PIPE_ACCEPT_REMOTE_CLIENTS(원격 연결을 받아 보안 기술자로 검사)와 PIPE_REJECT_REMOTE_CLIENTS(원격 클라이언트 연결을 자동 거절)에 대해. ↩ ↩2
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). 서버 측 스레드가 파이프에서 마지막으로 읽은 메시지의 클라이언트 보안 문맥에서 위장을 시작한다는 점, 완료 후 RevertToSelf로 돌아간다는 점, 위장 실패 후 이어가면 서버 프로세스 자신의(특권) 문맥에서 실행되므로 반환값을 항상 확인하고 실패 시 클라이언트의 요청을 실행해서는 안 된다는 점에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Client. 클라이언트가 CreateFile로 파이프를 연다는 점, 모든 인스턴스가 사용 중이면 ERROR_PIPE_BUSY가 나고 WaitNamedPipe로 빈자리를 기다린다는 점, 연 핸들의 기본이 바이트 읽기·블로킹·non-overlapped이며 SetNamedPipeHandleState로 메시지 읽기 모드로 바꿀 수 있다는 점에 대해. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. ReadFileEx / WriteFileEx를 통한 overlapped 작업, PeekNamedPipe를 통한 소비하지 않는 읽기, 메시지형 양방향 파이프에서 TransactNamedPipe가 요청 전송과 응답 수신을 한 호출로 한다는 점, 클라이언트가 시작하기 전의 블로킹 읽기가 경합을 일으킬 수 있다는 점에 대해. ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). NamedPipeServerStream / NamedPipeClientStream으로 연결하고 읽고 쓰는 점, PipeTransmissionMode.Message를 통한 메시지 단위 전송, 비동기 메서드로 여러 클라이언트를 처리하는 점에 대해. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Asynchronous로 비동기 I/O를 켜는 점, CurrentUserOnly가 같은 사용자(및 같은 상승 수준)의 프로세스와만 연결을 허용할 수 있다는 점에 대해. ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. 네임드 파이프 접근 권한의 구성, GENERIC_WRITE가 FILE_CREATE_PIPE_INSTANCE를 포함해 클라이언트에 generic write를 주면 서버 인스턴스 생성도 허용된다는 점, 데이터 읽기와 쓰기는 개별 접근 권한으로 부여한다는 점에 대해. ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Win32 스레드 풀 API ── CreateThreadpoolWork로 「스레드를 만들지 않는」병렬 처리
네이티브 코드에서 CreateThread를 여기저기 만들고 있지는 않은가요. Vista에서 개편된 Win32 스레드 풀 API의 work·timer·wait·io 네 객체, 클린업 그룹, 콜백에서 해서는 안 되는 일까지 1차 정보를 바탕으로 해설...
WMI/CIM을 C#・PowerShell에서 사용하기 ── 하드웨어 정보 취득・프로세스 감시・원격 조회 실무 가이드
PC 시리얼 번호 취득, 디스크 여유 공간 감시, 프로세스 시작 감지의 정석이 WMI/CIM입니다. Get-CimInstance 등 CIM 계열 명령어의 사용법과 구형 Get-WmiObject로부터의 이전, C#의 System.Management...
Windows의 TPM이란 무엇인가 ── 그림으로 이해하는 '키를 밖으로 내보내지 않는 금고'와 측정 부팅
TPM을 그림으로 해설합니다. 키를 칩 밖으로 내보내지 않는 구조, PCR과 측정 부팅, BitLocker·Windows Hello에서의 사용 방식, dTPM·fTPM·Pluton의 차이, Get-Tpm으로 확인하는 방법, 복구 키를 요구받았을 ...
자사 개발 Windows 앱이 바이러스로 오탐되었다면 ── Microsoft Defender 오탐 대응과 성능 영향에 대처하는 법
자사 개발 Windows 앱이 Microsoft Defender에 오탐되었을 때의 정규 대응 방법을 정리합니다. 현대 백신 소프트웨어의 구조, Microsoft에 대한 오탐 신고, 격리로부터의 복원, 제외 설정을 올바르게 넣는 방법과 그 위험까지...
Windows 앱의 중복 실행 방지 ── 네임드 Mutex와 재실행 시 창 활성화
업무용 Windows 앱의 정석 요건인 「같은 앱을 두 번 실행시키지 않는다」를 네임드 Mutex로 구현하는 방법을 정리합니다. Global\ 과 Local\ 네임스페이스의 차이로 인한 RDP 환경의 함정, 소유 스레드 제약과 AbandonedM...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- 네임드 파이프와 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)는 위장 토큰 기사에서 자세히 다룹니다.