PrivateServer
2026.06 – 2026.08 · 개인
C++ · Windows IOCP · Godot
C++, Windows IOCP로 만든 실시간 서버 프로젝트
주요 내용
- Windows IOCP 기반 NetworkRuntime으로 연결, 비동기 I/O 수명, 세션 상태 관리
- 60Hz fixed-step 서버 권위 World와 채널별 독립 Host
- C ABI를 거쳐 연결한 Godot C# 클라이언트, 이동은 클라이언트가 예측하고 서버가 보정
- 로컬에서 Host 2개 × 클라이언트 100개 부하 검증
구조
입력은 왼쪽에서 오른쪽으로, 판정 결과는 오른쪽에서 왼쪽으로 흐릅니다. 번호는 아래 설계 결정 번호와 같습니다.
설계 결정
-
① 세션 상태는 한 번에 한 Worker만 바꾼다
- 문제
- 여러 IOCP 작업 스레드가 같은 세션 상태를 동시에 바꿀 수 있음
- 선택
- 완료 이벤트를 세션별 Mailbox에 넣고 Actor Worker 하나만 처리, 실행 예약 누락은 Admission Gate(atomic + CAS)로 방지
- 트레이드오프
- 이벤트마다 Mailbox를 거치기 때문에 발생하는 Admission Gate 비용
-
② I/O 메모리는 용량을 정해 둔다
- 문제
- I/O context와 payload 메모리를 역할별로 제한하고 관측해야 함
- 선택
- 역할별 고정 블록 풀, 고갈은 PoolExhausted 예외로 명시적으로 처리하고 수명은 RAII로 관리
- 트레이드오프
- 블록 내부 단편화, 공유 free-list 경합으로 스레드 4개부터 new/delete보다 느림
-
③ 입력은 tick 경계에서 확정한다
- 문제
- 비동기로 도착하는 네트워크 이벤트를 바로 반영하면, 같은 tick 안에서도 도착 순서에 따라 판정 결과가 달라질 수 있음
- 선택
- 이벤트를 모았다가 tick 시작에 읽기 전용 입력으로 확정, 계산 결과는 Coordinator가 정한 순서로 반영
- 트레이드오프
- 입력이 한 tick 늦게 반영되고, tick 경계 안의 입력 순서까지는 보장하지 않음
-
④ payload 송신은 tick 계산을 블로킹하지 않아야 한다
- 문제
- 더블 버퍼를 사용해도 이전 송신이 끝날 때까지 다음 tick 계산이 블로킹됨
- 선택
- 트리플 버퍼를 사용해 송신 중, 대기, 계산용 버퍼 세 개를 동시에 사용
- 트레이드오프
- 고부하인 Peak 구간 p99는 5.78% 줄었지만, 부하가 낮은 Early·Late 구간 p99는 늘고(+0.22 ms, +1.01 ms) 메모리 +4.86 MiB
-
⑤ 네이티브 수명은 SafeHandle이 관리한다
- 문제
- C++ NetworkRuntime을 Godot C#에서 써야 하고, 네이티브 객체 수명이 C# 가비지 컬렉터와 어긋날 수 있음
- 선택
- opaque handle과 C 함수로 만든 C ABI 브리지, 수명은 SafeHandle이 소유하고 이벤트는 메인 스레드에서 가져옴
- 트레이드오프
- 경계를 넘을 때 payload를 복사해 소유권을 바꾸는 비용
검증
로컬 · 독립 World Host 2개 × 네이티브 클라이언트 100개 · 60Hz · 180초 · x64 Release · 3회 측정 중앙값
- 라운드 완료
- Host 2개 × 클라이언트 100개
- Peak(고부하) 구간 tick p99 (더블 → 트리플 버퍼)
- 16.607 → 15.648 ms
- tick 예산(16.67 ms) 초과 비율
- 0.954% → 0.629%
- 메모리(Working set)
- 58.25 → 63.11 MiB
플레이 흐름
-
1. 접속할 World Host 채널 선택
-
2. 닉네임 입력 후 접속, 서버가 플레이어 ID 발급
-
3. 이동은 클라이언트가 먼저 예측해 보여 주고, 판정은 서버가 맡음
-
4. 시뮬레이션에 참여하지 않는 Observer의 라운드 관측
-
5. 라운드 종료 후 순위와 승자 표시
관련 글
-
[C++] 고정 블록 메모리 풀: 메모리 풀은 더 빠를까?
고정 블록 메모리 풀을 new/delete와 비교합니다.
-
[C++] Actor Scheduling: Lost Wakeup을 막는 Admission Protocol
업데이트:Producer와 Consumer가 구분되어 있는 환경에서 발생할 수 있는 Lost Wakeup 에 대해 알아보고, 어떻게 해결할 수 있었는지 알아봅니다.
-
[C++] World Server 2: Double Buffer 도입기
업데이트:비동기 Runtime event를 authoritative tick 입력으로 확정하기 위해 Double Buffer를 도입하고, publication 병목을 Outbound Triple Buffer로 개선한 과정을 정리합니다.