🔥 .NET의 파이프 작업
https://learn.microsoft.com/en-us/dotnet/standard/io/pipe-operations
파이프는 프로세스 간 통신(Interprocess Communication, IPC)을 위한 수단을 제공합니다.
파이프에는 두 가지 유형이 있습니다:
- 익명 파이프 (Anonymous Pipes)
- 명명된 파이프 (Named Pipes)
✨ 익명 파이프 (Anonymous Pipes)
익명 파이프는 로컬 컴퓨터에서 프로세스 간 통신을 제공합니다.
익명 파이프는 명명된 파이프보다 오버헤드가 적지만 제공하는 서비스가 제한적입니다.
익명 파이프는 단방향이며 네트워크를 통해 사용할 수 없습니다. 또한 단일 서버 인스턴스만 지원합니다.
익명 파이프는 스레드 간 통신, 또는 파이프 핸들을 자식 프로세스가 생성될 때 쉽게 전달할 수 있는 부모-자식 프로세스 간 통신에 유용합니다.
.NET에서는 AnonymousPipeServerStream
및 AnonymousPipeClientStream
클래스를 사용하여 익명 파이프를 구현합니다.
익명 파이프 (Anonymous Pipes) 사용 방법
예제 1: 서버 프로세스 (부모 프로세스)
다음 예제는 익명 파이프를 사용하여 부모 프로세스에서 자식 프로세스로 문자열을 보내는 방법을 보여줍니다.
이 예제에서는 부모 프로세스에 PipeDirection.Out
값을 가진 AnonymousPipeServerStream
객체를 생성합니다.
그런 다음 부모 프로세스는 클라이언트 핸들을 사용하여 AnonymousPipeClientStream
객체를 생성하는 자식 프로세스를 시작합니다.
자식 프로세스는 PipeDirection.In
값을 가집니다.
이후 부모 프로세스는 사용자가 입력한 문자열을 자식 프로세스로 보냅니다.
해당 문자열은 자식 프로세스의 콘솔에 표시됩니다. 다음은 서버 프로세스(부모 프로세스) 코드입니다.
using System; using System.IO; using System.IO.Pipes; using System.Diagnostics; // Process 클래스를 사용하기 위해 추가 class PipeServer { static void Main() { Process pipeClient = new Process(); // 자식 프로세스를 제어할 Process 객체 생성 pipeClient.StartInfo.FileName = "pipeClient.exe"; // 실행할 자식 프로세스의 파일 이름 설정 // AnonymousPipeServerStream 객체 생성 // PipeDirection.Out: 서버 -> 클라이언트로 데이터를 보냄 (단방향) // HandleInheritability.Inheritable: 자식 프로세스가 이 파이프 핸들을 상속받을 수 있도록 설정 using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) { Console.WriteLine($"[SERVER] 현재 전송 모드: {pipeServer.TransmissionMode}."); // 자식 프로세스에게 서버 파이프의 클라이언트 핸들을 문자열로 전달합니다. pipeClient.StartInfo.Arguments = pipeServer.GetClientHandleAsString(); pipeClient.StartInfo.UseShellExecute = false; // 셸 실행 대신 직접 프로세스 시작 pipeClient.Start(); // 자식 프로세스 시작 // 자식 프로세스가 핸들을 상속받은 후, 부모 프로세스는 자신의 클라이언트 핸들 복사본을 해제합니다. // 이는 불필요한 리소스 유지를 방지합니다. pipeServer.DisposeLocalCopyOfClientHandle(); try { // 사용자 입력을 읽고 그 내용을 클라이언트 프로세스로 보냅니다. using (StreamWriter sw = new StreamWriter(pipeServer)) { sw.AutoFlush = true; // 매 쓰기 작업 후 스트림 자동 플러시 설정 // '동기화 메시지'를 클라이언트로 보내고 클라이언트가 이를 받을 때까지 기다립니다. sw.WriteLine("SYNC"); pipeServer.WaitForPipeDrain(); // 파이프에 기록된 모든 데이터가 전송될 때까지 대기 // 콘솔 입력을 클라이언트 프로세스로 보냅니다. Console.Write("[SERVER] 텍스트를 입력하세요: "); sw.WriteLine(Console.ReadLine()); } } // 파이프가 끊어지거나 연결이 해제될 때 발생하는 IOException을 잡습니다. catch (IOException e) { Console.WriteLine($"[SERVER] 오류: {e.Message}"); } } // using 블록을 벗어나면 pipeServer는 자동으로 Dispose됩니다. // 자식 프로세스가 종료될 때까지 기다립니다. pipeClient.WaitForExit(); // 자식 프로세스 객체를 닫습니다. pipeClient.Close(); Console.WriteLine("[SERVER] 클라이언트 종료. 서버도 종료합니다."); } }
예제 2: 클라이언트 프로세스 (자식 프로세스)
다음 예제는 클라이언트 프로세스를 보여줍니다.
서버 프로세스는 클라이언트 프로세스를 시작하고 해당 프로세스에 클라이언트 핸들을 전달합니다.
클라이언트 코드로부터 생성된 실행 파일은 pipeClient.exe
라는 이름으로 서버 실행 파일과 동일한 디렉토리에 복사되어야 서버 프로세스를 실행하기 전에 올바르게 동작합니다.
using System; using System.IO; using System.IO.Pipes; class PipeClient { static void Main(string[] args) { // 프로그램 인수가 있는지 확인합니다. // 서버가 클라이언트 핸들을 인수로 전달하기 때문에 필요합니다. if (args.Length > 0) { // AnonymousPipeClientStream 객체를 생성합니다. // PipeDirection.In: 클라이언트는 서버로부터 데이터를 받음 (단방향) // args[0]: 서버가 전달한 파이프 핸들 문자열 using (PipeStream pipeClient = new AnonymousPipeClientStream(PipeDirection.In, args[0])) { Console.WriteLine($"[CLIENT] 현재 전송 모드: {pipeClient.TransmissionMode}."); // 파이프 스트림에서 텍스트를 읽기 위한 StreamReader 생성 using (StreamReader sr = new StreamReader(pipeClient)) { string temp; // 서버로부터 '동기화 메시지'가 올 때까지 기다립니다. do { Console.WriteLine("[CLIENT] 동기화 대기 중..."); temp = sr.ReadLine(); } while (!temp.StartsWith("SYNC")); // 'SYNC'로 시작하는 메시지를 받을 때까지 반복 // 서버 데이터를 읽고 콘솔에 출력합니다. while ((temp = sr.ReadLine()) != null) // 스트림의 끝(null)이 아닐 때까지 읽기 { Console.WriteLine("[CLIENT] 에코: " + temp); } } } // using 블록을 벗어나면 pipeClient는 자동으로 Dispose됩니다. } Console.Write("[CLIENT] 계속하려면 Enter 키를 누르세요..."); Console.ReadLine(); // 사용자가 Enter를 누를 때까지 대기 } }
✨ 명명된 파이프 (Named Pipes)
명명된 파이프는 파이프 서버와 하나 이상의 파이프 클라이언트 간 프로세스 간 통신을 제공합니다.
명명된 파이프는 단방향 또는 양방향이 될 수 있습니다.
이는 메시지 기반 통신을 지원하며, 여러 클라이언트가 동일한 파이프 이름을 사용하여 서버 프로세스에 동시에 연결할 수 있도록 합니다.
명명된 파이프는 또한 가장(impersonation)을 지원하는데, 이를 통해 연결 프로세스가 원격 서버에서 자신의 권한을 사용할 수 있습니다.
.NET에서는 NamedPipeServerStream
및 NamedPipeClientStream
클래스를 사용하여 명명된 파이프를 구현합니다.
❓ 가장(Impersonation)이란?
컴퓨터 시스템에서 어떤 리소스(예: 파일, 네트워크 공유, 데이터베이스)에 접근하려면 해당 작업을 수행하는 사용자 계정의 권한이 필요
일반적으로 서버 애플리케이션은 특정 계정(예: 시스템 계정, 서비스 계정)의 권한으로 실행됩니다.
만약 서버가 클라이언트가 요청한 작업을 클라이언트 자신의 권한으로 수행해야 할 때가 있습니다. 예를 들어:
- 클라이언트가 접근 권한을 가진 특정 파일 읽기/쓰기:
서버는 클라이언트가 보낸 파일 경로에 대해 읽기/쓰기 요청을 받았습니다.
하지만 서버는 해당 파일에 직접 접근할 권한이 없고, 오직 클라이언트 계정만 그 파일에 접근할 권한을 가지고 있을 수 있습니다. - 클라이언트의 네트워크 자격 증명 사용:
클라이언트가 원격 네트워크 공유에 있는 리소스에 접근하고 싶을 때,
서버가 클라이언트를 ‘가장’하여 클라이언트의 네트워크 자격 증명(사용자 이름과 비밀번호)을 사용하여 해당 공유에 접근할 수 있습니다.
명명된 파이프(Named Pipes) 사용 방법
예제 1: NamedPipeServerStream
클래스를 사용하여 명명된 파이프 생성하기
다음 예제는 NamedPipeServerStream
클래스를 사용하여 명명된 파이프를 생성하는 방법을 보여줍니다.
이 예제에서 서버 프로세스는 4개의 스레드를 생성합니다. 각 스레드는 하나의 클라이언트 연결을 수락할 수 있습니다.
연결된 클라이언트 프로세스는 서버에 파일 이름을 제공합니다.
만약 클라이언트가 충분한 권한을 가지고 있다면, 서버 프로세스는 해당 파일을 열고 그 내용을 다시 클라이언트에게 보냅니다.
using System; using System.IO; using System.IO.Pipes; using System.Text; using System.Threading; public class PipeServer { // 동시에 처리할 서버 스레드의 개수 (클라이언트 연결을 수락할 인스턴스 수) private static int numThreads = 4; public static void Main() { int i; // 서버 스레드를 담을 배열 선언 Thread?[] servers = new Thread[numThreads]; Console.WriteLine("\n*** 명명된 파이프 서버 스트림과 가장(impersonation) 예제 ***\n"); Console.WriteLine("클라이언트 연결 대기 중...\n"); // numThreads 개수만큼 서버 스레드를 생성하고 시작합니다. for (i = 0; i < numThreads; i++) { servers[i] = new Thread(ServerThread); // ServerThread 메서드를 실행할 새 스레드 생성 servers[i]?.Start(); // 스레드 시작 } // 서버 스레드가 시작될 시간을 잠시 기다립니다. Thread.Sleep(250); // 모든 서버 스레드가 작업을 완료할 때까지 대기하는 루프 // 'i'는 아직 완료되지 않은 스레드의 개수를 추적합니다. while (i > 0) { for (int j = 0; j < numThreads; j++) { if (servers[j] != null) // 스레드가 아직 활성 상태인 경우 { // 스레드가 250ms 내에 완료되는지 확인합니다. // 완료되면 해당 스레드를 null로 설정하고 카운트를 줄입니다. if (servers[j]!.Join(250)) { Console.WriteLine($"서버 스레드[{servers[j]!.ManagedThreadId}] 작업 완료."); servers[j] = null; // 완료된 스레드 참조 제거 i--; // 완료되지 않은 스레드 개수 감소 } } } } Console.WriteLine("\n서버 스레드 모두 소진, 종료합니다."); } // 각 클라이언트 연결을 처리할 서버 스레드의 진입점 메서드 private static void ServerThread(object? data) { // NamedPipeServerStream 인스턴스 생성 // "testpipe": 파이프 이름 (클라이언트가 이 이름으로 연결 시도) // PipeDirection.InOut: 양방향 통신 // numThreads: 이 파이프 이름으로 동시에 허용할 서버 인스턴스(클라이언트 연결)의 최대 수 NamedPipeServerStream pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads); int threadId = Thread.CurrentThread.ManagedThreadId; // 현재 스레드의 ID // 클라이언트가 연결될 때까지 대기합니다. // 이 메서드는 블로킹(blocking) 호출입니다. pipeServer.WaitForConnection(); Console.WriteLine($"클라이언트가 스레드[{threadId}]에 연결되었습니다."); try { // 클라이언트로부터 요청을 읽습니다. // 클라이언트가 파이프에 데이터를 쓰면, 클라이언트의 보안 토큰을 사용할 수 있게 됩니다. StreamString ss = new StreamString(pipeServer); // 연결된 클라이언트에게 서버임을 증명하는 문자열을 보냅니다. // 클라이언트는 이 문자열을 예상하고 있습니다. ss.WriteString("나는 유일한 참된 서버입니다!"); // 클라이언트로부터 파일 이름을 읽습니다. string filename = ss.ReadString(); // 클라이언트를 가장(impersonate)하여 파일 내용을 읽어 스트림으로 보냅니다. // ReadFileToStream 클래스는 파일 읽기 로직을 캡슐화합니다. ReadFileToStream fileReader = new ReadFileToStream(ss, filename); // 현재 가장하고 있는 사용자의 이름을 출력합니다. Console.WriteLine($"파일 읽기: {filename} (스레드[{threadId}]) 사용자: {pipeServer.GetImpersonationUserName()}로."); // 클라이언트를 가장한 보안 컨텍스트 내에서 fileReader.Start 메서드를 실행합니다. // 이 시점부터 Start() 메서드 내의 파일 접근은 클라이언트의 권한으로 이루어집니다. pipeServer.RunAsClient(fileReader.Start); } // 파이프가 끊어지거나 연결이 해제될 때 발생하는 IOException을 잡습니다. catch (IOException e) { Console.WriteLine($"오류: {e.Message}"); } finally { // 파이프 서버 스트림을 닫고 리소스를 해제합니다. pipeServer.Close(); Console.WriteLine($"스레드[{threadId}] 파이프 서버 닫힘."); } } } // 스트림에서 문자열을 읽고 쓰기 위한 데이터 프로토콜을 정의하는 클래스 public class StreamString { private Stream ioStream; // 기본 스트림 (NamedPipeServerStream 또는 NamedPipeClientStream) private UnicodeEncoding streamEncoding; // 문자열 인코딩 방식 public StreamString(Stream ioStream) { this.ioStream = ioStream; streamEncoding = new UnicodeEncoding(); // UTF-16 인코딩 사용 } // 스트림에서 문자열을 읽습니다. public string ReadString() { // 먼저 문자열의 길이를 2바이트로 읽습니다. (Big-endian 방식으로) int len = 0; len = ioStream.ReadByte() * 256; // 첫 번째 바이트 (상위 8비트) len += ioStream.ReadByte(); // 두 번째 바이트 (하위 8비트) // 읽을 길이만큼의 바이트 배열을 생성합니다. byte[] inBuffer = new byte[len]; // 스트림에서 해당 길이만큼의 바이트를 읽어 버퍼에 채웁니다. ioStream.Read(inBuffer, 0, len); // 바이트 배열을 문자열로 디코딩하여 반환합니다. return streamEncoding.GetString(inBuffer); } // 스트림에 문자열을 씁니다. public int WriteString(string outString) { // 문자열을 바이트 배열로 인코딩합니다. byte[] outBuffer = streamEncoding.GetBytes(outString); int len = outBuffer.Length; // 문자열 길이가 UInt16.MaxValue (65535)를 초과하면 잘라냅니다. // (프로토콜이 2바이트 길이 필드를 사용하므로 최대 65535 바이트까지 표현 가능) if (len > UInt16.MaxValue) { len = (int)UInt16.MaxValue; } // 문자열의 길이를 2바이트로 스트림에 씁니다. (Big-endian 방식으로) ioStream.WriteByte((byte)(len / 256)); // 상위 바이트 ioStream.WriteByte((byte)(len & 255)); // 하위 바이트 // 문자열 바이트 배열을 스트림에 씁니다. ioStream.Write(outBuffer, 0, len); // 버퍼에 남아있는 데이터를 즉시 스트림으로 밀어 넣습니다. ioStream.Flush(); // 전송된 총 바이트 수 (문자열 바이트 + 길이 2바이트)를 반환합니다. return outBuffer.Length + 2; } } // 가장된 사용자(클라이언트)의 컨텍스트에서 실행될 메서드를 포함하는 클래스 public class ReadFileToStream { private string fn; // 읽을 파일 이름 private StreamString ss; // 파일 내용을 쓸 스트림 (StreamString 헬퍼 사용) public ReadFileToStream(StreamString str, string filename) { fn = filename; ss = str; } // 이 메서드는 pipeServer.RunAsClient()에 의해 호출되며, // 호출되는 동안 현재 스레드의 보안 컨텍스트가 클라이언트로 변경됩니다. public void Start() { // 클라이언트의 권한으로 파일을 읽습니다. string contents = File.ReadAllText(fn); // 읽은 파일 내용을 스트림을 통해 클라이언트에게 다시 보냅니다. ss.WriteString(contents); } }
예제 2: NamedPipeClientStream
클래스를 사용하는 클라이언트 프로세스
다음 예제는 NamedPipeClientStream
클래스를 사용하는 클라이언트 프로세스를 보여줍니다.
이 클라이언트는 서버 프로세스에 연결하여 서버로 파일 이름을 전송합니다.
이 예제에서는 가장(impersonation) 기능을 사용하므로, 클라이언트 애플리케이션을 실행하는 사용자 계정에 해당 파일에 접근할 수 있는 권한이 있어야 합니다.
그러면 서버는 파일 내용을 다시 클라이언트에게 전송하고, 클라이언트는 그 내용을 콘솔에 표시합니다.
using System; using System.Diagnostics; // 프로세스 관련 기능을 위해 추가 using System.IO; // 파일 및 스트림 관련 기능을 위해 추가 using System.IO.Pipes; // Named Pipe 관련 기능을 위해 추가 using System.Security.Principal; // TokenImpersonationLevel을 위해 추가 using System.Text; // 인코딩을 위해 추가 using System.Threading; // 스레드 및 Sleep을 위해 추가 public class PipeClient { // 생성할 클라이언트 프로세스의 개수 private static int numClients = 4; public static void Main(string[] args) { // 프로그램이 'spawnclient' 인수를 가지고 실행되었는지 확인합니다. // 이 인수는 메인 프로세스가 다른 클라이언트 프로세스를 시작할 때 사용됩니다. if (args.Length > 0) { if (args[0] == "spawnclient") { // 'spawnclient' 인수가 있는 경우, 명명된 파이프 클라이언트를 생성하고 서버에 연결합니다. var pipeClient = new NamedPipeClientStream( ".", // 파이프가 로컬 컴퓨터에 있음을 나타냅니다. (서버 주소) "testpipe", // 연결할 파이프 이름 (서버의 파이프 이름과 일치해야 합니다) PipeDirection.InOut, // 양방향 통신 PipeOptions.None, // 추가 파이프 옵션 없음 TokenImpersonationLevel.Impersonation); // 클라이언트의 보안 토큰을 서버로 전송하여 가장을 허용합니다. Console.WriteLine("서버에 연결 중...\n"); // 서버에 연결될 때까지 대기합니다. pipeClient.Connect(); var ss = new StreamString(pipeClient); // 서버의 서명 문자열("I am the one true server!")을 읽어 유효성을 검사합니다. if (ss.ReadString() == "I am the one true server!") { // 첫 번째 쓰기 작업과 함께 클라이언트의 보안 토큰이 서버로 전송됩니다. // 서버가 파일 내용을 반환할 파일 이름을 서버로 보냅니다. ss.WriteString("c:\\textfile.txt"); // 서버가 읽을 파일 경로를 지정합니다. // 서버로부터 파일 내용을 읽어 콘솔에 출력합니다. Console.Write(ss.ReadString()); } else { Console.WriteLine("서버를 확인할 수 없습니다."); } // 파이프 클라이언트 스트림을 닫고 리소스를 해제합니다. pipeClient.Close(); // 클라이언트 프로세스가 결과를 표시할 시간을 주기 위해 4초 동안 대기합니다. Thread.Sleep(4000); } } else // 'spawnclient' 인수가 없는 경우, 여러 클라이언트 프로세스를 시작합니다. { Console.WriteLine("\n*** 가장(impersonation) 예제를 포함한 명명된 파이프 클라이언트 스트림 ***\n"); // 클라이언트 프로세스를 시작하는 헬퍼 함수를 호출합니다. StartClients(); } } // 파이프 클라이언트 프로세스를 생성하는 헬퍼 함수 private static void StartClients() { // 현재 실행 중인 프로세스의 전체 명령줄을 가져옵니다. (예: "C:\path\to\YourApp.exe") string currentProcessName = Environment.CommandLine; // Visual Studio에서 실행될 때 추가되는 따옴표나 공백을 제거합니다. currentProcessName = currentProcessName.Trim('"', ' '); // 확장자를 .exe로 변경하여 실행 파일 경로를 확보합니다. currentProcessName = Path.ChangeExtension(currentProcessName, ".exe"); Process?[] plist = new Process?[numClients]; // 생성될 클라이언트 프로세스들을 저장할 배열 Console.WriteLine("클라이언트 프로세스들을 생성 중...\n"); // 현재 디렉토리 경로가 명령줄에 포함되어 있다면 제거합니다. // 이는 Process.Start가 올바른 실행 파일을 찾도록 돕기 위함입니다. if (currentProcessName.Contains(Environment.CurrentDirectory)) { currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty); } // Visual Studio 실행 시 발생할 수 있는 추가적인 문자(백슬래시, 따옴표)를 제거합니다. currentProcessName = currentProcessName.Replace("\\", String.Empty); currentProcessName = currentProcessName.Replace("\"", String.Empty); int i; for (i = 0; i < numClients; i++) { // '이' 프로그램(PipeClient) 자체를 새 프로세스로 시작하되, // 'spawnclient' 인수를 전달하여 명명된 파이프 클라이언트 모드로 실행되도록 합니다. plist[i] = Process.Start(currentProcessName, "spawnclient"); } // 모든 클라이언트 프로세스가 종료될 때까지 대기하는 루프 // 'i'는 아직 종료되지 않은 프로세스의 개수를 추적합니다. while (i > 0) { for (int j = 0; j < numClients; j++) { if (plist[j] != null) // 프로세스가 아직 활성 상태인 경우 { // 프로세스가 종료되었는지 확인합니다. if (plist[j]!.HasExited) { Console.WriteLine($"클라이언트 프로세스[{plist[j]?.Id}]가 종료되었습니다."); plist[j] = null; // 종료된 프로세스 참조 제거 i--; // 종료되지 않은 프로세스 개수 감소 } else { // 아직 종료되지 않았다면 잠시 대기합니다. Thread.Sleep(250); } } } } Console.WriteLine("\n클라이언트 프로세스들이 모두 완료되어 종료합니다."); } } // 스트림에서 문자열을 읽고 쓰기 위한 데이터 프로토콜을 정의하는 클래스 // (이 클래스는 서버 예제와 동일합니다) public class StreamString { private Stream ioStream; // 기본 스트림 (NamedPipeClientStream) private UnicodeEncoding streamEncoding; // 문자열 인코딩 방식 public StreamString(Stream ioStream) { this.ioStream = ioStream; streamEncoding = new UnicodeEncoding(); // UTF-16 인코딩 사용 } // 스트림에서 문자열을 읽습니다. public string ReadString() { // 먼저 문자열의 길이를 2바이트로 읽습니다. (Big-endian 방식으로) int len; len = ioStream.ReadByte() * 256; // 첫 번째 바이트 (상위 8비트) len += ioStream.ReadByte(); // 두 번째 바이트 (하위 8비트) // 읽을 길이만큼의 바이트 배열을 생성합니다. var inBuffer = new byte[len]; // 스트림에서 해당 길이만큼의 바이트를 읽어 버퍼에 채웁니다. ioStream.Read(inBuffer, 0, len); // 바이트 배열을 문자열로 디코딩하여 반환합니다. return streamEncoding.GetString(inBuffer); } // 스트림에 문자열을 씁니다. public int WriteString(string outString) { // 문자열을 바이트 배열로 인코딩합니다. byte[] outBuffer = streamEncoding.GetBytes(outString); int len = outBuffer.Length; // 문자열 길이가 UInt16.MaxValue (65535)를 초과하면 잘라냅니다. // (프로토콜이 2바이트 길이 필드를 사용하므로 최대 65535 바이트까지 표현 가능) if (len > UInt16.MaxValue) { len = (int)UInt16.MaxValue; } // 문자열의 길이를 2바이트로 스트림에 씁니다. (Big-endian 방식으로) ioStream.WriteByte((byte)(len / 256)); // 상위 바이트 ioStream.WriteByte((byte)(len & 255)); // 하위 바이트 // 문자열 바이트 배열을 스트림에 씁니다. ioStream.Write(outBuffer, 0, len); // 버퍼에 남아있는 데이터를 즉시 스트림으로 밀어 넣습니다. ioStream.Flush(); // 전송된 총 바이트 수 (문자열 바이트 + 길이 2바이트)를 반환합니다. return outBuffer.Length + 2; } }
강력한 프로그래밍: Named Pipe 클라이언트/서버 설정
이 예제에서 클라이언트와 서버 프로세스는 동일한 컴퓨터에서 실행되도록 설계되었습니다.
그래서 NamedPipeClientStream
객체에 제공된 서버 이름이 “.” 입니다.
만약 클라이언트와 서버 프로세스가 다른 컴퓨터에 있다면, “.” 은 서버 프로세스를 실행하는 컴퓨터의 네트워크 이름으로 대체되어야 합니다.
✅추가 정리
명명된 파이프에 대한 동작 개념
명명된 파이프는 파일 시스템의 ‘이름’이라는 개념을 빌려와 프로세스들이 서로를 찾아 연결할 수 있게 해주지만, 실제 데이터 교환은 메모리를 통해 이루어지는 IPC(프로세스 간 통신) 메커니즘입니다.
이러한 특성 덕분에 명명된 파이프는 로컬 컴퓨터 내의 여러 프로세스 간 통신에 매우 효율적이며, Windows에서는 네트워크를 통해 다른 컴퓨터의 파이프에도 연결할 수 있어 더욱 유연하게 사용할 수 있습니다.