[C# – 기초 강좌] 9-1. 통신(Communication) – IPC 통신(Named Pipe, Shared Memory)

이번 포스팅에서는 IPC(Inter-Process Communication)가 무엇인지에 대해 알아보고,

IPC 통신 방법 중 Named Pipe, Shared Memory가 무엇인지 그리고 예제를 통해 C#에서는 어떻게 사용할 수 있는지 알아보겠습니다.

IPC(Inter-Process Communication)란?

개요

IPC(Inter-Process Communication)는 컴퓨터 운영 체제에서 서로 다른 프로세스 간에 데이터를 주고받는 방법을 의미합니다. IPC는 여러 프로세스가 서로 정보를 교환하거나 리소스를 공유해야 할 때 사용됩니다. 일반적으로 IPC는 다음과 같은 상황에서 사용됩니다:

  1. 프로세스 간 데이터 교환: 서로 다른 프로세스가 데이터를 주고받아야 할 때.
  2. 프로세스 간 동기화: 프로세스들이 특정 이벤트를 기다리거나, 순서를 정해 실행되어야 할 때.
  3. 리소스 공유: 여러 프로세스가 메모리, 파일, 네트워크 소켓 등 특정 리소스를 공유해야 할 때.

IPC 특징

IPC는 다음과 같은 특징을 가지고 있습니다.

  • 프로세스 독립성 유지: 프로세스 간 통신은 서로 다른 프로세스의 실행 방식이나 메모리 공간에 영향을 미치지 않습니다.
  • 고속 통신: IPC는 동일한 시스템 내에서 실행되는 프로세스 간의 통신이므로 네트워크 통신보다 훨씬 빠릅니다. 이는 주로 메모리를 직접 공유하는 방식인 Shared Memory에서 두드러집니다.
  • 낮은 지연 시간: IPC는 프로세스 간 직접적인 통신을 가능하게 하므로, 통신의 지연 시간을 최소화할 수 있습니다. 이는 실시간 데이터 처리가 필요한 애플리케이션에서 매우 중요합니다.
  • 효율적인 자원 공유: IPC를 사용하면 여러 프로세스가 동일한 자원을 효율적으로 공유할 수 있습니다. 예를 들어, Named Pipe와 Shared Memory는 데이터의 중복 없이 자원을 공유할 수 있게 해줍니다.
  • 동기화 지원: IPC는 프로세스 간의 동기화를 지원하여, 여러 프로세스가 특정 이벤트를 기다리거나 순차적으로 실행될 수 있게 합니다. 이는 특히 동기화가 중요한 멀티스레드 애플리케이션에서 유용합니다.

IPC 통신 방법

IPC 통신 방법에는 여러 가지가 있지만, 이번 강좌에서는 Named Pipe, Message Queue, Shared Memory에 대해 중점적으로 다루겠습니다.

What-is-IPC

Named Pipe

개요

Named Pipe(명명된 파이프)는 프로세스 간 통신(IPC, Inter-Process Communication) 방법 중 하나로, Pipe에 이름을 명명하고, 해당 이름을 매개로 프로세스 간 통신할 수 있게 해줍니다.

Named Pipe는 유닉스 계열 운영 체제와 윈도우에서 모두 사용 가능하며, 특히 윈도우에서는 System.IO.Pipes 네임스페이스를 통해 쉽게 사용할 수 있습니다.

Named Pipe의 구조

Named Pipe는 서버와 클라이언트 모델을 사용하여 통신합니다.

서버는 파이프를 생성하고 클라이언트는 해당 파이프에 연결하여 데이터를 주고받습니다.

Named Pipe의 장점

  1. 간편한 사용:
    • 상대적으로 구현이 간단하며, 프로세스 간 데이터 통신을 쉽게 설정할 수 있습니다.
  2. 높은 성능:
    • 로컬 시스템에서 높은 성능을 제공합니다. 특히, 메모리 공유를 통해 데이터를 전달하는 Shared Memory와 비교해도 높은 성능을 보입니다.
  3. 보안 기능 제공:
    • 윈도우의 ACL(Access Control List)을 이용해 파이프에 대한 접근을 제어할 수 있어 보안성이 뛰어납니다.

Named Pipe의 단점

  1. 운영 체제 종속성:
    • 특정 운영 체제에 종속적인 기능이 있어, 윈도우와 유닉스 계열 시스템 간의 호환성 문제가 발생할 수 있습니다.
  2. 설정 복잡성:
    • 네트워크를 통한 Named Pipe 사용 시, 방화벽 설정 등 네트워크 관련 추가 설정이 필요할 수 있습니다.
  3. 리소스 관리:
    • 파이프가 열려 있는 동안 시스템 리소스를 사용하므로, 사용 후 적절한 리소스 해제가 필요합니다.

주요 네임스페이스

  • System.IO.Pipes:
    • Named Pipe와 관련된 모든 클래스를 포함하는 네임스페이스입니다.

주요 클래스

  1. NamedPipeServerStream:
    • Named Pipe 서버를 생성하고 클라이언트의 연결을 기다립니다.
  2. NamedPipeClientStream:
    • Named Pipe 클라이언트를 생성하고 서버에 연결합니다.
  3. PipeStream:
    • NamedPipeServerStream과 NamedPipeClientStream의 부모 클래스입니다.

주요 함수

  • PipeStream.Read(byte[] buffer, int offset, int count):
    • 파이프에서 데이터를 읽어옵니다.
  • PipeStream.Write(byte[] buffer, int offset, int count):
    • 파이프에 데이터를 씁니다.
  • NamedPipeServerStream.WaitForConnection():
    • 클라이언트의 연결을 기다립니다.
  • NamedPipeClientStream.Connect(int timeout):
    • 서버에 연결을 시도합니다.

예제 코드

서버(Server)

using System;
using System.IO.Pipes;
using System.Text;

namespace SharedMemoryReader
{
    class Program
    {
        static void Main(string[] args)
        {
            string message = "";

            while (message != "shutdown")
            {
                // Create Named Pipe Server Stream with the name "devitworld_pipe"
                using (NamedPipeServerStream pipeServer = new NamedPipeServerStream("devitworld_pipe", PipeDirection.In))
                {
                    Console.WriteLine("Named pipe server started.");
                    pipeServer.WaitForConnection(); // Wait for client connection
                    Console.WriteLine("Client connected.");
                    byte[] buffer = new byte[256];
                    int bytesRead = pipeServer.Read(buffer, 0, buffer.Length);  // Read message from client
                    message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                    Console.WriteLine("Received from client: " + message);
                }
            }
        }
    }
}

클라이언트(Client)

using System;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;

namespace SharedMemoryWriter
{
    class Program
    {
        static void Main(string[] args)
        {
            string message = "Hello from client!";

            while (message != "exit")
            {
                // Create Named Pipe Client Stream with the name "devitworld_pipe"
                using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "devitworld_pipe", PipeDirection.Out))
                {
                    try
                    {
                        // Connect to the server
                        Task connectTask = Task.Run(() => pipeClient.Connect());
                        if (connectTask.Wait(TimeSpan.FromSeconds(3)))
                        {
                            Console.WriteLine("Connected to server.");
                            Console.Write("Enter message: ");
                            message = Console.ReadLine();
                            byte[] buffer = Encoding.UTF8.GetBytes(message);
                            pipeClient.Write(buffer, 0, buffer.Length); // Send message to server
                            Console.WriteLine("Message sent to server.");
                        }
                        else
                        {
                            Console.WriteLine("Failed to connect to server. (Timeout)");
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Failed to connect to server. " + ex.Message);
                    }
                }
            }
            
        }
    }
}

결과

NamedPipe_Result

Shared Memory

개요

Shared Memory(공유 메모리)는 프로세스 간 통신(IPC, Inter-Process Communication) 방법 중 하나로, 여러 프로세스가 동일한 메모리 공간을 공유하여 데이터를 주고받을 수 있게 해줍니다.

Shared Memory는 특히 빠른 속도가 요구되는 상황에서 유용하며, 메모리의 특정 영역을 여러 프로세스가 읽고 쓸 수 있게 합니다.

Shared Memory의 구조

Shared Memory는 일반적으로 다음과 같은 단계를 통해 설정되고 사용됩니다:

  1. 메모리 맵 생성:
    • 프로세스가 메모리 맵을 생성하고 이를 공유합니다.
  2. 메모리 맵 열기:
    • 다른 프로세스가 이 메모리 맵을 열어 접근합니다.
  3. 데이터 읽기/쓰기:
    • 모든 프로세스가 이 메모리 맵에 접근하여 데이터를 읽고 쓸 수 있습니다.

Shared Memory의 장점

  • 고속 통신:
    • 프로세스 간 데이터를 메모리로 직접 주고받기 때문에 매우 빠른 통신이 가능합니다.
  • 낮은 지연 시간:
    • 메모리에 직접 접근하므로 지연 시간이 거의 없습니다.
  • 효율적인 자원 사용:
    • 메모리를 공유함으로써 데이터 복사를 줄이고, 효율적인 자원 사용이 가능합니다.

Shared Memory의 단점

  • 동기화 문제:
    • 여러 프로세스가 동시에 메모리에 접근할 때 동기화 문제(예: 레이스 컨디션, 데드락)가 발생할 수 있습니다.
  • 보안 문제:
    • 공유 메모리의 접근 권한을 적절히 설정하지 않으면, 데이터가 쉽게 노출될 수 있습니다.
  • 운영 체제 종속성:
    • 공유 메모리 구현은 운영 체제에 따라 다를 수 있습니다.

주요 네임스페이스

  • System.IO.MemoryMappedFiles:
    • .NET에서 공유 메모리를 사용하기 위한 클래스를 포함하는 네임스페이스입니다.

주요 클래스

  • MemoryMappedFile:
    • 공유 메모리 파일을 생성하고 관리하는 클래스입니다.
  • MemoryMappedViewAccessor:
    • 공유 메모리 파일의 뷰를 액세스하는 클래스입니다.
  • MemoryMappedViewStream:
    • 공유 메모리 파일의 스트림을 액세스하는 클래스입니다.

주요 함수

  • MemoryMappedFile.CreateNew(string mapName, long capacity):
    • 새로운 메모리 맵을 생성합니다.
  • MemoryMappedFile.OpenExisting(string mapName):
    • 기존의 메모리 맵을 엽니다.
  • MemoryMappedFile.CreateViewAccessor():
    • 메모리 맵의 뷰에 접근할 수 있는 뷰 액세서를 생성합니다.
  • MemoryMappedViewAccessor.Read<T>(long position):
    • 특정 위치에서 데이터를 읽어옵니다.
  • MemoryMappedViewAccessor.Write<T>(long position, T value):
    • 특정 위치에 데이터를 씁니다.

예제 코드

서버(Server)

using System;
using System.IO.MemoryMappedFiles;
using System.Text;
using System.Threading;

namespace SharedMemoryServer
{
    class Program
    {
        static void Main(string[] args)
        {
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("devitworld_shared_memory", 1024))
            {
                Console.WriteLine("Shared memory server started.");

                while (true)
                {
                    // Read message from client
                    using (var accessor = mmf.CreateViewAccessor())
                    {
                        byte[] buffer = new byte[1024];
                        accessor.ReadArray(0, buffer, 0, buffer.Length);
                        string message = Encoding.UTF8.GetString(buffer).TrimEnd('\0');
                        Console.WriteLine("Received from client: " + message);

                        if (message == "shutdown")
                        {
                            break;
                        }

                        // make empty the shared memory
                        accessor.WriteArray(0, new byte[1024], 0, 1024);
                    }

                    Thread.Sleep(2000);
                }
            }
        }
    }
}

클라이언트(Client)

using System;
using System.IO.MemoryMappedFiles;
using System.Text;

namespace SharedMemoryClient
{
    class Program
    {
        static void Main(string[] args)
        {
            // Open existing shared memory with the name "devitworld_shared_memory"
            try
            {
                using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("devitworld_shared_memory"))
                {
                    string message = "Hello from client!";

                    while (message != "exit")
                    {
                        Console.Write("Enter message: ");
                        message = Console.ReadLine();
                        byte[] buffer = Encoding.UTF8.GetBytes(message);

                        using (var accessor = mmf.CreateViewAccessor())
                        {
                            accessor.WriteArray(0, buffer, 0, buffer.Length);
                            Console.WriteLine("Message sent to server.");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to open shared memory. " + ex.Message);
            }
        }
    }
}

결과

SharedMemory_Result

참고 링크

Leave a Comment