A301 MMO Game Server - 프로젝트 보고서
1. 프로젝트 개요
| 항목 |
내용 |
| 프로젝트명 |
A301 MMO Game Server |
| 언어 |
C# (.NET 9.0) |
| 네트워크 |
LiteNetLib (UDP 기반) |
| 직렬화 |
Protocol Buffers (protobuf-net) |
| 데이터베이스 |
MySQL (Dapper ORM) |
| 로깅 |
Serilog |
| 배포 |
Docker Compose |
2. 프로젝트 구현 구성
2.1 디렉토리 구조
2.2 핵심 모듈 구성
ServerLib (네트워크 코어 라이브러리)
| 모듈 |
파일 |
역할 |
| ServerBase |
ServerBase.cs |
추상 서버 - 연결/해제/패킷 수신 이벤트 처리 |
| Session |
Session.cs |
인증 상태 + Peer 매핑 (hashKey, token) |
| PacketSerializer |
PacketSerializer.cs |
4바이트 헤더(type 2B + size 2B) + Protobuf 페이로드 |
| ARepository |
ARepository.cs |
제네릭 비동기 CRUD (Dapper.Contrib 기반) |
| CrashDumpHandler |
CrashDumpHandler.cs |
예외 로깅 + 메모리 힙 덤프 생성 |
MMOserver (게임 로직)
| 모듈 |
파일 |
역할 |
| GameServer |
GameServer.cs |
패킷 라우팅 + 게임 로직 처리 (ServerBase 상속) |
| Player |
Player.cs |
플레이어 데이터 (위치, HP/MP, 닉네임 등) |
| Channel |
Channel.cs |
채널 단위 플레이어 그룹 관리 |
| ChannelManager |
ChannelManager.cs |
전역 채널 관리 (Singleton) |
| RestApi |
RestApi.cs |
JWT 토큰 검증 (외부 인증 API 연동) |
| UuidGeneratorManager |
UuidGeneratorManager.cs |
Lock-free 고유 ID 생성기 |
2.3 패킷 프로토콜 정의
| 코드 |
패킷명 |
전송 방식 |
설명 |
| 1 |
ACC_TOKEN |
Reliable |
실제 JWT 토큰 인증 |
| 1001 |
DUMMY_ACC_TOKEN |
Reliable |
더미 클라이언트 인증 |
| 1000 |
ECHO |
Reliable |
네트워크 테스트 (Ping) |
| 2 |
LOAD_CHANNEL |
Reliable |
채널 목록 응답 |
| 3 |
INTO_CHANNEL |
Reliable |
채널 입장 |
| 5 |
UPDATE_CHANNEL_USER |
Reliable |
유저 입장/퇴장 알림 |
| 6 |
TRANSFORM_PLAYER |
Unreliable |
이동/회전 동기화 |
| 7 |
ACTION_PLAYER |
Reliable |
공격/스킬/회피 액션 |
| 8 |
STATE_PLAYER |
Reliable |
HP/MP 상태 동기화 |
2.4 데이터베이스 계층 (RDB)
아키텍처 패턴: Handler → Service → Repository → DB
2.5 스레드 모델
| 구분 |
방식 |
설명 |
| 네트워크 루프 |
싱글 스레드 |
netManager.PollEvents() 1ms 주기 호출 |
| 패킷 처리 |
동기 처리 |
Poll 루프 내에서 순차적 처리 |
| JWT 검증 |
async/await |
HttpClient 비동기 호출 |
| DB 연산 |
async/await |
Dapper 비동기 쿼리 |
| UUID 생성 |
lock 기반 |
스레드 안전 ID 할당 |
3. 파이프라인
3.1 클라이언트 접속 ~ 게임 플레이 전체 흐름
3.2 패킷 처리 파이프라인
3.3 세션 관리 파이프라인
- 재접속 처리: 동일 hashKey로 재접속 시 기존 Peer 강제 해제 → 새 Peer로 교체
- 채널 유지: 재접속 시 기존 채널 멤버십 유지 (채널 선택 스킵)
3.4 빌드 및 배포 파이프라인
4. 버그 및 잠재적 이슈
코드 정적 분석을 통해 발견된 버그 및 잠재적 문제점 목록입니다.
4.1 버그 목록
[B1] Engine Vector3 타입 불일치 (HIGH)
- 파일:
MMOserver/Game/Engine/Vector3.cs vs MMOserver/Packet/PacketBody.cs
- 문제: 두 Vector3 클래스가 다른 타입으로 정의됨
Engine/Vector3.cs: 좌표가 int 타입
Packet/PacketBody.cs (PacketVector3): 좌표가 float 타입
- 영향: 실제 코드에서는 Packet Vector3만 사용되고 Engine Vector3는 사용되지 않음. 향후 Engine Vector3를 사용하는 코드가 생기면 타입 캐스팅 오류 발생
- 권장 조치: Engine Vector3 제거 또는 float 기반으로 통일
[B2] ChannelManager.AddUser() 스레드 안전성 문제 (MEDIUM)
- 파일:
MMOserver/Game/Channel/ChannelManager.cs
- 문제: 비동기 인증 흐름(JWT 검증 → AddUser) 후 두 요청이 동시에 같은 userId로
connectUsers.Add()를 호출할 경우 ArgumentException 발생 가능
- 영향: 동시 재접속 시나리오에서 서버 크래시 가능성
- 권장 조치:
TryAdd() 사용 또는 lock 추가
[B3] PlayerId 정수 오버플로우로 인한 중복 ID (LOW)
- 파일:
MMOserver/Game/GameServer.cs
- 문제:
PlayerId = (int)(hashKey & 0x7FFFFFFF) 계산으로 서로 다른 hashKey가 동일한 PlayerId를 생성할 수 있음
- 예:
hashKey=1 → PlayerId=1, hashKey=2147483649 → PlayerId=1 (동일)
- 영향: ID 기반 플레이어 조회 오작동
- 권장 조치: PlayerId로 hashKey 원본(long) 사용
[B4] TokenHash 딕셔너리 무한 증가 (MEDIUM)
- 파일:
ServerLib/Service/ServerBase.cs
- 문제: 토큰 만료 또는 로그아웃 시
tokenHash 딕셔너리에서 항목이 제거되지 않음. 장기 운영 시 모든 누적 토큰이 메모리에 남음
- 영향: 장기 운영 서버에서 메모리 누수 (ex. 100만 로그인 이후 100만 항목 유지)
- 권장 조치: 로그아웃/연결 해제 시
tokenHash.Remove() 호출, 또는 TTL 기반 만료 캐시 도입
[B5] 패킷 역직렬화 크기 검증 없음 (MEDIUM)
- 파일:
ServerLib/Service/ServerBase.cs
- 문제: 패킷 페이로드가
null인지는 확인하지만, 역직렬화된 객체의 필드 크기는 검증하지 않음
- 영향: 악의적으로 구성된 패킷(예: PlayerInfo 리스트 내 수만 개의 항목)이 메모리 과소비 유발 가능
- 권장 조치: 역직렬화 전 페이로드 바이트 크기 상한 검증 추가
[B6] Echo 패킷에 레이트 리미팅 미적용 (MEDIUM)
- 파일:
MMOserver/Game/GameServer.cs
- 문제: Echo 패킷 처리 경로가 세션 인증 및 레이트 리미팅 검사를 우회함
- 영향: 인증 전 클라이언트가 Echo 패킷을 무제한으로 전송하여 서버 자원 소모 가능 (DoS)
- 권장 조치: Echo 처리 경로에도 레이트 리미팅 적용
5. 최적화 미비점
5.1 성능 병목
[O1] BroadcastToChannel 내 반복적 딕셔너리 조회 ✅ 해결됨
- 해결:
Channel에 Dictionary<long, NetPeer> connectPeers 추가, BroadcastToChannel이 GetConnectPeers()를 직접 순회하도록 변경
- 변경 파일:
Channel.cs, ChannelManager.cs, GameServer.cs
- 효과: 100명 채널 브로드캐스트 시
sessions 딕셔너리 교차 조회 100회 → 0회 제거
- 추가 처리: 재연결(WiFi→LTE) 시
Channel.UpdatePeer()로 peer 참조 갱신
[O2] Transform 패킷 객체 풀링 미구현
- 파일:
ServerLib/Packet/PacketSerializer.cs
- 문제: 매 패킷마다 새 객체 생성 및 Protobuf 리플렉션 수행. Transform 패킷은 초당 수백~수천 회 발생하는 고빈도 패킷
- 권장 조치:
ObjectPool<T> 도입으로 GC 압박 감소
[O3] Dictionary 열거 시 Enumerator 힙 할당
- 현황: 모든 열거 메서드(
GetConnectUsers, GetPlayers, GetConnectPeers)를 IEnumerable<T>로 통일
- 판단 근거: 박싱 비용(수 나노초)이 실질적으로 무시 가능한 수준이며,
IEnumerable<T>가 가독성과 인터페이스 일관성 면에서 유리
- 미적용 사유: 가독성 > 마이크로 최적화 우선
[O4] 레이트 리미터 고정 윈도우 방식
- 파일:
ServerLib/Service/Session.cs
- 문제: 현재 고정 윈도우(Fixed Window) 방식 사용. 윈도우 경계에서 최대 2배 패킷 폭주 허용 가능
- 예: 윈도우 끝 120개 + 새 윈도우 시작 120개 = 240개/초 순간 처리
- 권장 조치: 슬라이딩 윈도우 또는 토큰 버킷 알고리즘으로 교체
[O5] 로깅 문자열 할당
- 현황: 모든 패킷 처리마다 Serilog 메시지 템플릿 평가 발생
- 권장 조치:
if (Log.IsEnabled(LogEventLevel.Debug)) 가드 추가로 Debug 레벨 불필요한 평가 방지
6. 문제점 - 해결
6.1 해결된 문제
[P1] 이동 패킷 재전송 문제
- 문제: 이동(Transform) 패킷이
ReliableOrdered로 전송되어 패킷 손실 시 재전송 발생 → 위치 동기화 지연, Head-of-Line 블로킹
- 해결: 이동 패킷을
Unreliable 전송으로 변경 (commit: 42f0ef1)
- 효과: 실시간성 향상, 네트워크 지연 최소화 (오래된 위치 데이터는 폐기)
[P2] 로그인 인증 실패 시 재시도 부재
- 문제: JWT 토큰 검증 API 호출이 일시적 네트워크 오류로 실패 시 즉시 인증 실패 처리
- 해결: 3회 재시도 로직 추가 (1초 간격), 401 응답은 즉시 실패 (
commit: c8ce36a)
- 효과: 일시적 API 장애에 대한 내결함성 확보
[P3] 토큰 → HashKey 매핑 구조
- 문제: 토큰과 hashKey 간의 매핑이 명확하지 않아 재접속/중복 접속 처리 어려움
- 해결:
TokenHash 딕셔너리 도입, 토큰 관리 로직 구조화 (commit: bfa3394)
- 효과: 동일 토큰 재접속 시 기존 세션 정리 및 hashKey 재활용
[P4] 에코 클라이언트 수신 버그
- 문제: 에코 테스트 클라이언트에서 수신 패킷 처리 오류
- 해결: 수신 로직 버그 수정 (
commit: 18fd8a0)
[P5] 서버 크래시 시 디버깅 정보 부족
- 문제: 서버 비정상 종료 시 원인 파악 불가
- 해결:
CrashDumpHandler 구현 (commit: 2be1302)
- Debug 빌드:
.log 파일 (스택 트레이스)
- Release 빌드:
.log + .dmp 파일 (힙 덤프)
- UnhandledException, UnobservedTaskException 모두 캡처
- 효과: 프로덕션 환경 크래시 사후 분석 가능
[P6] 패킷 레이트 리미팅 부재
- 문제: 클라이언트의 패킷 전송 빈도 제한 없음 → 패킷 플러딩 공격에 취약
- 해결:
Session 클래스에 슬라이딩 윈도우 방식 레이트 리미터 구현
- 초당 최대 120 패킷 허용 (
MaxPacketsPerSecond)
- 초과 시 해당 패킷 드롭 + 경고 로그
- 3회 연속 초과 시 강제 연결 해제
ServerBase.OnNetworkReceive()에서 인증된 패킷 처리 전 체크
- 적용 파일:
Session.cs, ServerBase.cs
- 효과: 악의적 패킷 플러딩 방어, 서버 안정성 향상
[P7] 부하 테스트 도구 부재
- 문제: 대규모 동시접속 시나리오 검증 도구 없음, 기존 더미 클라이언트는 10명 고정
- 해결:
StressTest 모듈 신규 구현 (ClientTester/EchoClientTester/StressTest/)
StressTestClient.cs: 이동 + Echo RTT 동시 측정 클라이언트
StressTestService.cs: 점진적 Ramp-up, 퍼센타일 레이턴시(P50/P95/P99), CSV 내보내기
Program.cs: CLI 인자 지원 (stress -c 100 -d 60 --ip ...) + 대화형 모드 3번 메뉴
- 효과: N명 동시접속 시나리오 자동 검증, 성능 병목 지점 파악
6.2 현재 남아 있는 문제
[I1] 서버 사이드 위치 검증 미구현
- 현상: 클라이언트가 전송하는 위치를 서버에서 검증하지 않음
- 영향: 스피드핵, 텔레포트 등 치트 방어 불가
- 권장:
MapBounds 로직을 서버 사이드에 적용, 비정상 이동 감지 시 보정
[I2] 플레이어 데이터 DB 연동 미완성
- 현상: 플레이어 데이터가 하드코딩된 기본값 사용 (GameServer.cs 내 TODO 주석 존재)
- 영향: 캐릭터 정보 저장/불러오기 불가, 레벨·닉네임·HP/MP 등 지속성 없음
- 권장: Player 모델 DB 테이블 생성, 로그인 시 DB 조회
[I3] NPC / 파티 시스템 미구현
- 현상: 패킷 코드(
TRANSFORM_NPC, ACTION_NPC, UPDATE_PARTY 등)는 정의되어 있으나 핸들러 없음
- 영향: 수신 시 경고 로그만 출력되고 무시됨
- 권장: NPC AI 시스템, 파티 매칭 로직 순차 구현
7. 정리
7.1 아키텍처 요약
7.2 기술적 강점
| 항목 |
내용 |
| UDP 기반 통신 |
LiteNetLib으로 TCP 오버헤드 없이 실시간 통신 |
| Protobuf 직렬화 |
컴팩트 바이너리 인코딩으로 대역폭 절약 |
| 이동 패킷 Unreliable |
HOL 블로킹 방지, 최신 위치만 유효 |
| 싱글 스레드 이벤트 루프 |
Lock 불필요, 동시성 이슈 최소화 |
| 비동기 DB/API |
async/await로 네트워크 루프 블로킹 방지 |
| 크래시 덤프 |
프로덕션 환경 사후 분석 지원 |
| 계층화된 DB 구조 |
Handler-Service-Repository 패턴 |
| Docker 배포 |
컨테이너화된 일관된 배포 환경 |
7.3 향후 작업 (Roadmap)
| 우선순위 |
작업 |
설명 |
| 높음 |
플레이어 DB 연동 |
캐릭터 정보 저장/불러오기 완성 (I2) |
| 높음 |
버그 수정 |
B2 스레드 안전성, B4 메모리 누수, B6 Echo 레이트 리미팅 |
| 높음 |
서버 사이드 위치 검증 |
치트 방어를 위한 이동 유효성 검사 (I1) |
| 중간 |
NPC 시스템 |
AI 기반 NPC 이동/전투 로직 (I3) |
| 중간 |
성능 최적화 |
O1 브로드캐스트 최적화 (완료), O2 객체 풀링, O4 레이트 리미터 개선 |
| 낮음 |
파티 시스템 |
파티 생성/참여/매칭 (I3) |
| 낮음 |
모니터링 대시보드 |
서버 상태/접속자 수 실시간 모니터링 |
완료된 항목: 패킷 레이트 리미팅 (적용 완료), 부하 테스트 도구 (구현 완료), 크래시 덤프 (적용 완료), O1 브로드캐스트 최적화 (적용 완료)
7.4 부하 테스트 도구 (StressTest)
ClientTester/EchoClientTester/StressTest/ 에 구현됨.
구성 파일
| 파일 |
역할 |
StressTestClient.cs |
개별 스트레스 클라이언트 (이동 + Echo RTT 동시 측정) |
StressTestService.cs |
테스트 오케스트레이터 (Ramp-up, 통계, CSV 내보내기) |
기능
| 기능 |
설명 |
| 점진적 Ramp-up |
N초 간격으로 클라이언트 추가 (서버 부하 단계적 증가) |
| 지속시간 제한 |
테스트 자동 종료 (초 단위, 0 = 무제한) |
| 실시간 통계 |
10초마다 접속수, 전송/수신 rate, AvgRTT 출력 |
| 퍼센타일 레이턴시 |
P50, P95, P99, Min, Max, Avg RTT 계산 |
| 최악 클라이언트 분석 |
RTT 상위 5명 상세 리포트 |
| CSV 내보내기 |
results/stress_{N}clients_{timestamp}.csv 자동 저장 |
사용법
CLI 모드:
대화형 모드:
출력 예시
7.5 최근 개발 이력
| 커밋 |
내용 |
85c3276 |
세션 끊길 때 같은 채널에 퇴장 메시지 전송 |
c27e846 |
채널 버그 수정 |
d4c5a70 |
채널 버그 수정 |
ea3f64a |
스트레스 테스트 기능 추가 / 패킷 처리량 제한 / 프로젝트 상황 README 추가 |
2be1302 |
크래시 덤프 기능 추가 (Release: 힙 덤프) |
42f0ef1 |
이동 패킷 Unreliable 전송으로 변경 |
bfa3394 |
토큰 → HashKey 생성 로직 구조 변경 |
c8ce36a |
로그인 실패 시 재시도 로직 추가 |
18fd8a0 |
에코 클라이언트 수신 버그 수정 |
작성일: 2026-03-06
프로젝트 상태: 핵심 네트워크 인프라 완성, 게임 콘텐츠 확장 단계
구현 완료: 네트워크 코어, 세션 관리, 채널 시스템, 인증, 레이트 리미팅, 크래시 덤프, 부하 테스트 도구
미구현: 플레이어 DB 연동, NPC/파티 시스템, 서버 사이드 위치 검증, 전투/아이템 시스템
알려진 버그: B1 Vector3 타입 불일치, B2 ChannelManager 스레드 안전성, B4 TokenHash 메모리 누수, B6 Echo DoS 취약점