본문 바로가기

[단계별로 IOCP 실습] 1단계 Echo Server 만들기

@iamrain2025. 7. 31. 16:35

IOCP를 공부하려고 인터넷을 검색하다가 종종 알고리즘에 뜨던 최홍배님의 영상을 보고 따라해보려고 한다.

https://www.youtube.com/watch?v=RMRsvll7hrM

IOCP 학습

먼저 IOCP가 뭔지 알아보자. IOCP라고 검색해보면 많은 글을 볼 수 있는데 정리하면 이렇다.

IOCP는 Input/Ouptput Completion Port의 약자로 소켓이나 파일의 입출력을 최소한의 스레드를 사용해서 처리하는 기법이다.
Overlapped I/O 방식에 스레드 풀링(Thread Pooling)과 큐(Queue) 메커니즘을 결합한 효율적인 비동기 처리 기술이다.

Overlapped I/O와 IOCP의 차이

일반 Overlapped I/O는 비동기 작업 완료 시 이벤트나 콜백을 통해 직접 알리는 방식입니다. 반면 IOCP는 작업 완료 시 즉시 알리지 않고, 커널의 IOCP Queue에 완료된 작업 결과를 저장한 후, Completion Port 객체를 통해 사용자에게 전달합니다.

이 과정에서 CreateIoCompletionPort() 함수로 소켓(또는 파일 핸들)과 컴플리션 키를 관리하는 별도의 Device List를 구성하여 효율성을 높입니다.

IOCP는 커널이 관리하는 스레드 풀을 통해 완료된 Overlapped I/O 작업을 Queue에서 꺼내 처리합니다.

IOCP 동작 흐름

IOCP 주요 함수

  • CreateIoCompletionPort()
    Completion Port 객체를 생성하며, 해당 객체에 소켓(또는 파일 핸들)과 사용자 정의 데이터(컴플리션 키)를 연결한다.
  • GetQueuedCompletionStatus()
    Completion Port 객체를 통해 IOCP Queue에서 완료된 작업 결과를 가져온다.
  • PostQueuedCompletionStatus()
    사용자가 직접 IOCP Queue에 임의 작업을 추가하는 함수로, 주로 서버 종료 시 대기 중인 스레드를 깨워 정상 종료를 유도할 때 사용한다.

IOCP Echo Server 구현하기

Echo Server를 구현하는게 1단계지만 아직 어려우니 코드 분석을 해보자.

enum class와 구조체

enum class IOOperation
{
    RECV,
    SEND
};

struct stOverlappedEx
{
    WSAOVERLAPPED m_wsaOverlapped;
    SOCKET        m_socketClient;
    WSABUF        m_wsaBuf;
    char        m_szBuf[ MAX_SOCKBUF ];
    IOOperation m_eOperation;
};    

struct stClientInfo
{
    SOCKET            m_socketClient;
    stOverlappedEx    m_stRecvOverlappedEx;
    stOverlappedEx    m_stSendOverlappedEx;

    stClientInfo()
    {
        ZeroMemory( &m_stRecvOverlappedEx , sizeof( stOverlappedEx ) );
        ZeroMemory( &m_stSendOverlappedEx , sizeof( stOverlappedEx ) );
        m_socketClient = INVALID_SOCKET;
    }
};
  • `enum class IOOperation`
    • 비동기 I/O 작업의 종류를 구분하기 위한 열거형 클래스
    • `RECV`
      데이터 수신(Receive) 작업을 나타낸다.
    • `SEND`
      데이터 송신(Send) 작업을 나타낸다.
    • `stOverlappedEx` 구조체 내에 이 열거형 변수를 두어, 완료된 I/O 작업을 식별하는 데 사용된다.
  • `struct stOverlappedEx`
    • Windows의 기본 `WSAOVERLAPPED` 구조체를 확장하여 비동기 I/O 작업에 필요한 추가 정보를 저장하는 구조체
    • `WSAOVERLAPPED m_wsaOverlapped`
      비동기 I/O 작업의 상태 정보를 저장하는 Windows 표준 구조체다. 운영체제가 이 구조체를 직접 사용한다.
    • `SOCKET m_socketClient`
      이 I/O 작업이 발생한 클라이언트 소켓의 핸들이다.
    • `WSABUF m_wsaBuf`
      데이터 버퍼의 위치와 크기를 담는 구조체다. `m_szBuf`를 가리키도록 설정해 사용한다.
    • `char m_szBuf[ MAX_SOCKBUF ]`
      실제 데이터를 송수신하는 버퍼다. 크기는 `MAX_SOCKBUF`로 정의된 1024바이트다.
    • `IOOperation m_eOperation`
      이 `OVERLAPPED` 작업이 `RECV`인지 `SEND`인지 구분하는 값이다.
  • `struct stClientInfo`
    • 서버에 접속한 클라이언트 한 명에 대한 모든 정보를 통합하여 관리하는 구조체
    • `SOCKET m_socketClient`
      해당 클라이언트와 연결된 소켓 핸들이다.
    • `stOverlappedEx m_stRecvOverlappedEx`
      해당 클라이언트의 수신 작업을 위한 `stOverlappedEx` 구조체다. `WSARecv` 함수 호출 시 이 구조체가 사용된다.
    • `stOverlappedEx m_stSendOverlappedEx`
      해당 클라이언트의 송신 작업을 위한 `stOverlappedEx` 구조체다. `WSASend` 함수 호출 시 이 구조체가 사용된다.
    • `stClientInfo()` : `stClientInfo` 구조체의 생성자
      • `ZeroMemory(...)`
        구조체가 생성될 때 내부에 있는 `stOverlappedEx` 멤버 변수들의 메모리 영역을 0으로 초기화
      • `m_socketClient = INVALID_SOCKET`
        소켓 핸들을 `INVALID_SOCKET`(-1)으로 초기화하여, 아직 유효한 클라이언트가 할당되지 않은 상태임을 명시

IOCP Echo Server 동작 흐름

  1. 소켓 초기화
  2. bind
  3. listen
  4. IOCP 오브젝트 생성

`Accept` 스레드에서

  1. 접속 승인 (클라이언트 소켓 생성)
  2. IOCP 오브젝트에 클라이언트 소켓 등록
  3. RECV 작업 요청

`Woker` 스레드에서

  1. RECV 작업 완료 (클라이언트에서 SEND)
  2. RECV 작업 후 수신 데이터 출력
  3. 클라이언트에 SEND 작업 수행 (Echo)
  4. 다시 RECV 작업 요청
  5. 1 - 4 과정을 반복

위의 과정을 코드로 구현하면 된다.

IOCompletionPort Class의 구현

  • `InitSocket()` : `Listen` 소켓 생성
    • `WSAStartup`, `WSASocket` 2가지 함수를 호출하여 소켓을 초기화 한다.
  • `BindandListen()` : 클라이언트의 접속을 받기 위한 과정을 수행
    • 서버의 주소정보를 `SOCKADDR_IN` 구조체에 기입
    • `Listen` 소켓과 주소 정보를 `bind`
    • 접속 요청을 받기 위해 `listen` 함수 호출
  • `StartServer()` :  IOCP 핸들을 생성하고 각 쓰레드를 활성화
    • `CreateIoCompletionPort` : IOCP 핸들 생성
    • WokerThread 활성화 - IO 작업 수행
    • AccepterThread 활성화 - Accept 작업 수행
  • `WokerThread`의 `main` : IO의 완료정보를 받아서 IO 작업을 수행. stOverlappedEx 구조체의 기입된 정보를 바탕으로 작업을 진행 
    • `GetQueuedCompletionStatus()` 함수로 IO의 완료정보를 받아온다.
    • 만약 완료라면? `stOverlappedEx`의 `IOOperation` 값을 비교하여 이후 작업을 수행한다.
    • `IOOperation`의 값이 `RECV`라면? `stOverlappedEx` 구조체의 버퍼를 확인하여 출력. 받은 버퍼를 그대로 송신(Echo)
    • `IOOperation`의 값이 `SEND`라면? 송신한 내용을 출력
  • `AccepterThread`의 `main` :  클라이언트 접속을 받는 작업 진행 후 IOCP 오브젝트에 클라이언트 소켓을 등록. 클라이언트와 통신을 진행할 준비를 마치는 작업
    • `accept` 함수 호출
    • 클라이언트가 접속 되었다면? IOCP 오브젝트에 클라이언트 소켓 등록
    • `Recv Overlapped IO` 작업 요청
iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차