IOCP 기반 다중 접속 및 데이터 처리-1

반응형

윈도 환경에서 다중 클라이언트의 접속을 처리하는 네트워크 서버를 구현하기 위해서는 IOCP 사용이 필수라고 생각합니다. 에코 서버를 IOCP 기반으로 구현한 예제는 많지만 실제 현업에서 다중 클라이언트 접속을 처리하기 위한 아키텍처를 어떤 방식으로 접근하면 좋을지 고민해 보는 차원에서 IOCP 기반 다중 접속 처리 및 데이터 처리 프로토타입 구상을 위해 이번 포스팅을 작성합니다.

 

1. 리눅스 epoll 대비 IOCP(I/O Completion Port) 장점

대부분의 유명한 게임서버는 Windows Server 기반이라는 사실을 알고 계신가요? 

그 이유는 리눅스에서 다중 접속을 처리하기 위해 사용가능한 epoll 보다 Windows의 IOCP가 더 나은 장점이 있기 때문입니다. 

  • 윈도 표준 API로 윈도 응용 프로그램과의 호환성 
  • 이벤트 큐 탐색 방식이 epoll에 비해 효율적이고 이벤트 큐 크기 제한이 없어 대규모 I/O 작업 처리에 적합
  • 스레드 풀과 함께 사용하기 편리합니다.

2. IOCP 프로토타입 소스파일 구조

다음과 같이 프로그램의 프로토타입을 클래스별로 구조화 하여 스켈레톤 코드를 작성하였습니다.

프로토타입 소스코드 파일

  • DmssItem Class: 패킷버퍼를 사전 할당한 메모리풀로 관리하는 클래스
  • RilCbWorker Class: 다중 클라이언트가 전송한 패킷을 처리 후 콜백함수 호출하는 스레드 풀 구성
  • RilServer Class: 부모 클래스로서 매니저 스레드, Iocp 스레드, Tx 스레드를 생성
  • RilSession Class: 다중 접속 클라이언트의 소켓을 관리하고 세션을 관리
  • ril_protocol Header: 클라이언트와 서버가 서로 공유하는 프로토콜 정의 헤더 파일

2.1. RIL Server 설명

Radio Interface Layer(이하 RIL) 모듈의 부모 클래스로서 RIL을 구성하는 하위 모듈의 인스턴스를 갖고 자식 스레드를 관리합니다.

RIL Server Class

2.2. RIL Session 설명

다중 클라이언트를 관리하기 위한 클래스입니다.

링크드리스트로 관리합니다.

클라이언트가 접속을 끊으면 소켓을 종료하지 않고 재사용하여 오버헤드를 줄입니다.

클라이언트가 전송중인 패킷을 임시 저장할 공간을 가지고 있습니다. 일종의 링버퍼 역할인데 프로토콜에 특화된 자료구조를 적용하여 링버퍼의 탐색 단위 1바이트보다 더 큰 블록 단위로 처리합니다.

아래쪽의 CMap 클래스는 미리 생성해 둔 소켓을 관리하는 목적입니다. 클라이언트가 접속하는 순간에 소켓을 생성하는 것이 아니고 미리 생성해 둔 소켓 풀에서 소켓을 가져오고 접속이 종료되면 소켓을 닫는 것이 아니라 소켓풀에 다시 넣어둡니다. 소켓을 재사용하면 클라이언트의 빈번한 접속/종료에 대한 성능 오버헤드를 줄일 수 있는 장점이 있지만 재사용된 소켓을 같은 클라이언트로 인식하지 않도록 별도의 관리 기법 적용이 필요합니다.

RIL Session Data Structure

위 자료구조를 사용하여 아래의 세션 리스트를 구성합니다.

RIL Session List

세션 클래스 코드는 아래와 같이 작성하였습니다.

RIL Session Class

2.3. RIL Callback Worker

수신데이터를 처리하는 스레드로서 스레드 풀을 구성합니다. 스레드 풀의 규모는 동적으로 결정됩니다.

그래서 CWinThread** _m_pparrThread, HANDLE* _m_phThreads; 로 포인터 변수를 두고 CPU의 코어 수에 의해 스레드풀 규모가 결정되면 그때 동적으로 할당 후 스레드를 생성합니다.

RIL Callback Worker Class

2.4. DMSS Item Class

DMSS는 Data Management Services(DMSs)의 약자입니다.

패킷 데이터 송수신에 사용하는 자료구조 TST_DSM_ITEM을 정의하고 DSM_ITEM POOL을 관리합니다. 프로그램 시작시 정적 메모리 영역에 할당이 되었다가 동작 중 풀 용량이 부족하면 힙 메모리 영역에서 추가로 할당하여 사용합니다. IDLE 타임에는 남는 노드를 메모리 반환합니다.

TST_DSM_ITEM
TST_DSM_ITEM_POOL
TST_DSM_HEADER_ITEM
DMSS Item Class

2.5. RIL 프로토콜 헤더

네트워크 프로그래밍 서적 또는 게임 서버 개발 서적을 보면 서버와 클라이언트 통신을 위한 필수 과정으로 패킷의 직렬화/역직렬화 이야기가 나옵니다. 개발 공수는 더 들지만 아래와 같이 1바이트로 패딩 한 구조체를 사용하면 가장 빠릅니다.

RIL PROTOCOL 헤더
RIL PROTOCOL 헤더 2

2.6. 소켓 유틸리티

비동기 소켓을 IOCP와 연계 활용하기 위한 유틸 함수입니다. 아래 정의해둔 NTSTATUS 값을 참고하기 바랍니다.

SOCK_ITEM
GetListenSocket

비동기 소켓 API는 직접 호출이 아니라 API 함수에 대한 포인터 획득 후 함수포인터로 호출하는 방식입니다. 그래서 아래의 유틸 함수를 사용합니다. 윈도 시스템 프로그래밍 책에서 발췌하였습니다.

GetSockExtAPI

앞서 소켓풀을 사용한다고 말씀드렸습니다. 아래 함수는 소켓풀에 있는 소켓이 전부 소진되었을 때 추가로 소켓풀에 소켓을 할당하는 함수입니다.

IncreaseAcceptSockets

 

이상으로 IOCP기반 다중 접속 처리를 위한 프로토타입을 작성해 보았습니다.

반응형