IT

[Server] IOCP

wakelight23 2024. 12. 23. 23:04

Input/Output Completion Port (IOCP)

Windows 운영 체제에서 비동기 입출력 작업을 처리하기 위한 작동 과정 중 하나

 

● 고성능 네트워크 서버나 파일 처리 애플리케이션에서 사용됨

 

적은 수의 스레드로 다수의 클라이언트 요청을 효율적으로 처리할 수 있도록 설계된
   이벤트 기반 큐 시스템

 

Overlapped I/O?

Overlapped, 중첩 된다는 뜻으로, 하나의 스레드가 한 번에 하나의 I/O만을 처리하는 것이 아닌,
   여러 개의 IO를 다루는 기술

 

기존 동기식 IO는 하나 요청 한 후 완료될 때까지 기다려야함

   → 비동기 IO 사용하면, 입출력 요청 보낸 이후 나중에 그 입출력 결과 받을 수 있다.

 

Overlapped I/O → IOCP

● IOCP는 Overlapped I/O에서 사용하는 'OVERAPPED' 구조체와 관련 함수들을 그대로 사용

  → 비동기 작업 완료 정보를 관리하고 처리 가능

 

● Overlapped I/O는 작업 완료 여부를 확인하기 위해 호출 스레드가 직접 확인 or 대기

  → 예) WSAaitForMultipleEvents

 

● 반면 IOCP는 완료된 정보를 Completion Port에 등록하고, Worker Thread Pool에서
   이를 처리하는 방식으로 동작

 

● 즉, IOCP는 Overlapped I/O의 단점을 보완한 고성능 모델

 

 

예시 코드로 맛만...

1. IOCP 오브젝트 생성

 - 'CreateIoCompletionPort' 함수를 호출 → IOCP 오브젝트 생성

HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

 

2. 디바이스(소켓, 파일 등)와 IOCP 연결

 - 입출력을 수행할 디바이스를 IOCP와 연결

 - 동일한 'CreateIoCompletionPort' 함수로 디바이스 핸들과 Completion Key를 바인딩

예) Socket 연결 ▼

CreateIoCompletionPort((HANDLE)socket, hIOCP, (ULONG_PTR)completionKey, 0);

 

3. 비동기 I/O 요청

 - Overlapped I/O를 사용하여 비동기 작업을 요청

 - 여기서 'OVERLAPPED' 구조체 사용

예) 데이터 송수신할 때

WSASend(socket, &wsaBuf, 1, &bytesSent, 0, &overlapped, NULL);

 

4. 작업 완료 통지

 - 비동기 작업이 완료되면 IOCP는 해당 작업 정보를 내부 큐(Completion Queue)에 저장

 - 이 큐에서 대기 중인 스레드가 작업 정보를 가져감

예) ▼

DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED pOverlapped;

GetQueuedCompletionStatus(hIOCP, &bytesTransferred, &completionKey, &pOverlapped, INFINITE);

 

 

 

쓰레드 풀 작동 원리

 ○ IOCP 내부 구조 정리

   1) IOCompletionQueue : FIFO 큐로서 완료된 입출력 작업이 저장.
          → 대기 중인 스레드가 이 큐에서 작업을 가져옴

   2) WaitingThreadQueue : LIFO 큐로 대기 중인 스레드가 저장.

          → 새로운 작업이 도착하면 이 큐에서 스레드를 꺼내 사용

   3) ReleaseThreadList : 현재 실행 중인 스레드 목록.

          → 작업이 끝난 스레드는 다시 WaitingThreadQueue로 돌아감

   4) PausedThreadList : 일시정지된 스레드가 저장.

          → ReleaseThreadList에 여유공간이 생길 때까지 대기

 

Echo Server 간단 구현

1. 서버 초기화

 - Socket 생성하고 Binding 및 Listen 설정을 수행

 - IOCP 오브젝트 생성하고 Listen 소켓을 IOCP에 등록

// IOCP 기반 Echo Server의 주요 흐름
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); // IOCP 생성

 

2. 클라이언트 연결 처리

 - 클라이언트 연결 요청을 수락(Accept)한 뒤 생성된 클라이언트 소켓을 IOCP에 등록

// 클라이언트 소켓 등록
CreateIoCompletionPort((HANDLE)clientSocket, hIOCP, (ULONG_PTR)clientKey, 0);

 

3. 데이터 송수신

 - 클라이언트로부터 데이터를 비동기로 수신('WSARecv')하고,
   수신된 데이터를 다시 클라이언트에게 송신('WSASend')

// 데이터 수신 요청
WSARecv(clientSocket, &recvBuffer, 1, &bytesReceived, &flags, &overlappedRecv, NULL);

 

4. 작업 완료 처리

 - 'GetQueuedCompletionStatus'로 입출력 완료 정보를 가져와 적절히 처리하고 다음 작업 요청

// 완료 통지 대기 및 처리
DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED pOverlapped;

while (true) {
    BOOL result = GetQueuedCompletionStatus(hIOCP, &bytesTransferred, &completionKey,
                                            &pOverlapped, INFINITE);
    if (result && bytesTransferred > 0) {
        // Echo 처리: 받은 데이터를 다시 송신
        WSASend(clientSocket, &sendBuffer, 1, &bytesSent,
                flags, pOverlappedSend, NULL);
    }
}

 

5. 전체 흐름

// IOCP 기반 Echo Server의 주요 흐름
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); // IOCP 생성

// 클라이언트 소켓 등록
CreateIoCompletionPort((HANDLE)clientSocket, hIOCP, (ULONG_PTR)clientKey, 0);

// 데이터 수신 요청
WSARecv(clientSocket, &recvBuffer, 1, &bytesReceived, &flags, &overlappedRecv, NULL);

// 완료 통지 대기 및 처리
DWORD bytesTransferred;
ULONG_PTR completionKey;
LPOVERLAPPED pOverlapped;

while (true) {
    BOOL result = GetQueuedCompletionStatus(hIOCP, &bytesTransferred, &completionKey,
                                            &pOverlapped, INFINITE);
    if (result && bytesTransferred > 0) {
        // Echo 처리: 받은 데이터를 다시 송신
        WSASend(clientSocket, &sendBuffer, 1, &bytesSent,
                flags, pOverlappedSend, NULL);
    }
}

 

IOCP란 뭐다?

- IOCP는 Overlapped I/O와 결합하여 높은 성능과 확장성을 제공하는 Windows의 비동기 I/O 모델이다.

- 다수의 클라이언트를 효율적으로 처리하며, 네트워크 서버와 같은 고성능 애플리케이션에 적합하다.