[TIL] 소켓 프로그래밍 - IOCP

KYJ의 Tech Velog·2024년 3월 7일
0

epoll은 논블로킹 소켓이 많이 있을 때 효율적으로 처리해주는 API입니다.

이에 대응하여 Overlapped I/O를 다루는 운영체제(Window)에서 만든 것이 I/O Completion Port(IOCP)입니다. (사실 시기적으로는 IOCP는 1993년, epoll은 2002년에 등장했다고 합...)

IOCP는 소켓의 Overlapped I/O가 완료되면 이를 감지해서 사용자에게 알려주는 역할을 합니다. 특정 소켓의 I/O가 완료되면 내장된 큐에 이 상황을 넣고, 사용자는 이 큐에서 완료 신호를 꺼낼 수 있습니다. 소켓 개수가 엄청 많더라도 완료된 것들만 얻을 수 있기 때문에 모든 소켓을 확인할 필요가 없는 것입니다.

epoll과 동작 방식은 크게 다르지 않습니다. 차이점이 있다면 epoll은 I/O 가능인 것을 알려주지만 IOCP는 I/O 완료인 것을 알려준다는 점입니다.

또한, epoll에 비해 오래된 API이기 때문에 비교적 복잡한 기능이 몇 가지 있습니다. 그 중 하나가 승인 처리입니다.

  1. IOCP에 listen socket L을 추가했다면, L에서 TCP 연결을 받을 경우 이에 대한 완료 신호가 IOCP에 추가
  2. 단, 사전에 AcceptEx로 Overlapped I/O를 건 상태
  3. IOCP로 L에 대한 이벤트로 얻어 왔지만, Overlapped 승인처럼 SO_UPDATE_ACCEPT_CONTEXT와 관련된 처리를 해주어야 새 TCP 소켓 핸들 획득 가능

반면, epoll에 비해 성능상으로 유리하는 기능도 있습니다. IOCP는 스레드 풀을 쉽게 구현할 수 있습니다.

epoll은 I/O 여부와 상관없이 I/O 가능 이벤트가 옵니다. 한 epoll에 대해 여러 스레드가 동시에 이벤트가 발생하는 것을 기다린다고 가정해봅시다. epoll과 연동된 소켓 하나가 UDP 데이터 2개를 수신 큐에 갖고 있다 생각해봅시다. epoll 이벤트는 같은 소켓에 대해 두 스레드에서 동시에 꺼내집니다. 각 스레드는 같은 소켓에 대해 UDP 논블로킹 수신을 하게 되겠죠. 이 때 데이터 순서는 알기가 어렵습니다.

순서를 알더라도 두 스레드가 동시에 같은 일을 하므로, 처리의 우선순위를 정리해줄 로직이 필요합니다. 복잡해지겠죠.

IOCP는 이러한 문제가 없습니다. IOCP는 어떤 소켓에 대해 Overlapped I/O를 하지 않는 이상 그 소켓에 대한 완료 신호는 발생하지 않습니다. 즉, 한 소켓에 대한 완료 신호를 한 스레드만 처리할 수 있도록 보장합니다. 덕분에 IOCP 하나를 여러 스레드가 기다리게 구현하는 것은 간단합니다. 또한, 많은 소켓에 대한 I/O 처리를 동시에 수행할 때, 여러 스레드가 신호 처리를 골고루 나누어서 처리할 수 있습니다. 이는 동시접속자가 많은 게임 서버를 만들기 위해 멀티 코어를 모두 활용해야 할 때 유리합니다.

물론 epoll에서 스레드 풀링을 구현하는 것도 가능하긴 합니다.

  • 스레드 개수만큼 epoll 객체 생성
  • 각 스레드는 자기만의 epoll을 처리
  • 여러 소켓은 이 epoll 중 하나에만 배정

다만, IOCP에 비해서 균형 있게 신호를 분담해주는 효율적인 성능은 보여주지 못하죠.

epoll을 사용하는 리눅스에서는 TCP 소켓으로 수신을 한 후에 데이터 수신을 하려면 소켓 수신 함수를 이어서 호출해줘야 합니다. 반면, IOCP를 사용하는 윈도우 서버에서는 연결 받기와 수신을 소켓 함수 호출 한 번으로 끝낼 수 있습니다. 부가적으로 연결된 소켓의 endpoint 정보를 얻는 것도 같이 끝낼 수 있어 프로그램 최적화에 유리하죠.

윈도우 서버리눅스 서버
AceeptExaccept
GetAcceptExSockaddrsrecv
getsockname
getpeername

앞선 표와 같이 윈도우 서버의 커널 함수 호출이 더 적습니다. 게임 서버는 한 번 TCP 연결을 맺으면 게임 클라이언트가 나갈 때까지 거의 유지되지만, HTTP처럼 메시징을 할 때마다 TCP 연결을 맺는 상황에서는 윈도우 서버가 더 유리합니다.

0개의 댓글