# 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 디렉토리 구조 ``` a301_mmo_game_server/ ├── MMOTestServer/ │ ├── MMOserver/ # 게임 서버 실행 프로젝트 │ │ ├── Api/ │ │ │ └── RestApi.cs # JWT 토큰 검증 (외부 API 호출) │ │ ├── Game/ │ │ │ ├── GameServer.cs # 메인 게임 서버 (ServerBase 상속) │ │ │ ├── Player.cs # 플레이어 엔티티 (상태 + 위치) │ │ │ ├── Channel/ │ │ │ │ ├── Channel.cs # 단일 게임 채널 │ │ │ │ ├── ChannelManager.cs # 채널 관리 (Singleton) │ │ │ │ └── Maps/ │ │ │ │ └── Robby.cs # 로비 맵 │ │ │ └── Engine/ │ │ │ └── Vector3.cs # 3D 위치 구조체 │ │ ├── Packet/ │ │ │ ├── PacketHeader.cs # PacketCode Enum 정의 │ │ │ └── PacketBody.cs # 모든 패킷 클래스 (ProtoContract) │ │ ├── RDB/ # 데이터베이스 계층 │ │ │ ├── DbConnectionFactory.cs │ │ │ ├── Models/ │ │ │ ├── Repositories/ │ │ │ ├── Services/ │ │ │ └── Handlers/ │ │ ├── Utils/ │ │ │ ├── Singleton.cs │ │ │ └── UuidGeneratorManager.cs │ │ ├── Program.cs # 서버 진입점 │ │ └── config.json # DB 접속 설정 │ │ │ ├── ServerLib/ # 핵심 네트워크 라이브러리 (DLL) │ │ ├── Service/ │ │ │ ├── ServerBase.cs # 추상 서버 베이스 클래스 │ │ │ ├── Session.cs # 세션 상태 관리 │ │ │ └── SessionManager.cs # 세션 매니저 │ │ ├── Packet/ │ │ │ ├── PacketSerializer.cs # Protobuf 직렬화 (4바이트 헤더) │ │ │ └── PacketType.cs # 기본 패킷 타입 │ │ ├── RDB/ │ │ │ ├── Database/ │ │ │ │ └── IDbConnectionFactory.cs │ │ │ ├── Repositories/ │ │ │ │ └── ARepository.cs # 제네릭 비동기 Repository 베이스 │ │ │ └── Handlers/ │ │ │ ├── HelperHandler.cs │ │ │ └── Response.cs │ │ └── Utils/ │ │ └── CrashDumpHandler.cs # 크래시 덤프 핸들러 │ │ │ └── MMOserver.sln │ ├── ClientTester/ # 클라이언트 테스트 도구 │ └── EchoClientTester/ │ ├── DummyService/ # 더미 플레이어 시뮬레이션 │ │ ├── DummyClientService.cs │ │ ├── DummyClients.cs │ │ └── MapBounds.cs │ ├── EchoDummyService/ # Echo/Ping 테스트 │ │ ├── EchoDummyClientService.cs │ │ └── EchoDummyClients.cs │ └── Packet/ │ ├── compose.yaml # Docker Compose (MySQL + Server) └── ReadMe.md # DB 아키텍처 문서 ``` ### 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` ``` [Handler] API 요청 수신 / JSON 응답 반환 ↓ [Service] 비즈니스 로직 (검증, 변환) ↓ [Repository] 데이터 접근 (Dapper CRUD) ↓ [MySQL] 커넥션 풀 (Min: 5, Max: 100) ``` ### 2.5 스레드 모델 | 구분 | 방식 | 설명 | |------|------|------| | 네트워크 루프 | **싱글 스레드** | `netManager.PollEvents()` 1ms 주기 호출 | | 패킷 처리 | 동기 처리 | Poll 루프 내에서 순차적 처리 | | JWT 검증 | async/await | `HttpClient` 비동기 호출 | | DB 연산 | async/await | Dapper 비동기 쿼리 | | UUID 생성 | lock 기반 | 스레드 안전 ID 할당 | --- ## 3. 파이프라인 ### 3.1 클라이언트 접속 ~ 게임 플레이 전체 흐름 ``` [1] 클라이언트 접속 (UDP) │ ├─ LiteNetLib ConnectionRequest │ └─ ServerBase.OnConnectionRequest() → 연결 키 검증 │ ▼ [2] 인증 (Authentication) │ ├─ 클라이언트 → 서버: ACC_TOKEN 또는 DUMMY_ACC_TOKEN 패킷 전송 │ ├─ [실제 인증] JWT 토큰 → RestApi.VerifyTokenAsync() │ └─ POST https://a301.api.tolelom.xyz/api/auth/verify │ ├─ 성공 → username 반환, hashKey 생성 │ ├─ 401 → 즉시 실패 (토큰 무효) │ └─ 기타 오류 → 3회 재시도 (1초 간격) │ ├─ [더미 인증] hashKey 직접 수신 (≤1000 범위) │ ├─ Session 객체 생성 (hashKey, token, peer) │ └─ peer.Tag에 Session 저장 │ ▼ [3] 채널 선택 │ ├─ 서버 → 클라이언트: LOAD_CHANNEL (채널 목록) ├─ 클라이언트 → 서버: INTO_CHANNEL (채널 선택) │ ├─ 서버 처리: │ ├─ 채널에 플레이어 추가 │ ├─ 기존 플레이어 목록 조회 │ └─ 서버 → 클라이언트: INTO_CHANNEL 응답 (기존 플레이어 리스트) │ ├─ 서버 → 채널 전체: UPDATE_CHANNEL_USER (새 플레이어 입장 알림) │ ▼ [4] 게임 루프 (실시간 동기화) │ ├─ TRANSFORM_PLAYER [Unreliable] │ └─ 위치(X,Y,Z) + 회전(RotY) → 채널 내 브로드캐스트 │ ├─ ACTION_PLAYER [ReliableOrdered] │ └─ 공격/스킬/회피 → 채널 내 브로드캐스트 │ ├─ STATE_PLAYER [ReliableOrdered] │ └─ HP/MP 상태 변경 → 채널 내 브로드캐스트 │ ▼ [5] 연결 종료 │ ├─ 클라이언트 → 서버: EXIT_CHANNEL ├─ 서버: 채널에서 플레이어 제거 ├─ 서버 → 채널 전체: UPDATE_CHANNEL_USER (퇴장 알림) └─ 세션 정리 및 Peer 해제 ``` ### 3.2 패킷 처리 파이프라인 ``` 수신 (OnNetworkReceive) ↓ ┌─────────────────────────────────────┐ │ 4바이트 헤더 파싱 │ │ ├─ type: ushort (2B) → 패킷 종류 │ │ └─ size: ushort (2B) → 페이로드 크기 │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ Protobuf 역직렬화 │ │ └─ byte[] → 패킷 객체 │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 타입별 분기 │ │ ├─ ECHO (1000) → HandleEcho() │ │ ├─ AUTH (1/1001) → HandleAuth() │ │ └─ GAME (2~8) → HandlePacket() │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 게임 로직 처리 │ │ └─ 채널 내 브로드캐스트 │ └─────────────────────────────────────┘ ↓ 송신 (SendTo / BroadcastToChannel) ``` ### 3.3 세션 관리 파이프라인 ``` PendingPeers (미인증) Sessions (인증 완료) Dictionary Dictionary 접속 시 등록 ──────────────→ 인증 성공 시 이동 (peerId 기반) (hashKey 기반) TokenHash (토큰 매핑) Dictionary token → hashKey 조회 ``` - **재접속 처리**: 동일 hashKey로 재접속 시 기존 Peer 강제 해제 → 새 Peer로 교체 - **채널 유지**: 재접속 시 기존 채널 멤버십 유지 (채널 선택 스킵) ### 3.4 빌드 및 배포 파이프라인 ``` 소스 코드 ↓ dotnet build (MMOserver.sln) ├─ ServerLib.csproj → ServerLib.dll └─ MMOserver.csproj → MMOserver.exe ↓ Docker Build (Dockerfile) ↓ Docker Compose (compose.yaml) ├─ MySQL 컨테이너 └─ MMOServer 컨테이너 └─ UDP 포트: 9050, 9500 ``` --- ## 4. 문제점 - 해결 ### 4.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명 동시접속 시나리오 자동 검증, 성능 병목 지점 파악 ### 4.2 현재 남아 있는 문제 #### [I1] 서버 사이드 위치 검증 미구현 - **현상**: 클라이언트가 전송하는 위치를 서버에서 검증하지 않음 - **영향**: 스피드핵, 텔레포트 등 치트 방어 불가 - **권장**: `MapBounds` 로직을 서버 사이드에 적용, 비정상 이동 감지 시 보정 #### [I2] 플레이어 데이터 DB 연동 미완성 - **현상**: 플레이어 데이터가 하드코딩된 기본값 사용 (TODO 주석 존재) - **영향**: 캐릭터 정보 저장/불러오기 불가 - **권장**: Player 모델 DB 테이블 생성, 로그인 시 DB 조회 #### [I3] NPC / 파티 시스템 미구현 - **현상**: 패킷 코드(TRANSFORM_NPC, ACTION_NPC, UPDATE_PARTY 등)는 정의되어 있으나 처리 로직 없음 - **영향**: 게임 콘텐츠 부족 - **권장**: NPC AI 시스템, 파티 매칭 로직 순차 구현 --- ## 5. 정리 ### 5.1 아키텍처 요약 ``` ┌──────────────────────────────────────────────────────┐ │ 클라이언트 (Unity) │ └──────────────┬───────────────────────────┬────────────┘ │ UDP (LiteNetLib) │ ┌──────────────▼───────────────────────────▼────────────┐ │ ServerBase (네트워크 코어) │ │ ┌────────────┐ ┌────────────┐ ┌─────────────────┐ │ │ │ 연결 관리 │ │ 패킷 직렬화 │ │ 세션 관리 │ │ │ └────────────┘ └────────────┘ └─────────────────┘ │ └──────────────┬───────────────────────────┬────────────┘ │ │ ┌──────────────▼───────────────┐ ┌────────▼────────────┐ │ GameServer (게임 로직) │ │ RestApi (인증) │ │ ┌─────────┐ ┌────────────┐ │ │ JWT 토큰 검증 │ │ │ Channel │ │ Player │ │ │ (외부 API 호출) │ │ │ Manager │ │ State │ │ └─────────────────────┘ │ └─────────┘ └────────────┘ │ └──────────────┬───────────────┘ │ ┌──────────────▼───────────────┐ │ RDB Layer (데이터베이스) │ │ Handler → Service → Repo │ │ → MySQL │ └──────────────────────────────┘ ``` ### 5.2 기술적 강점 | 항목 | 내용 | |------|------| | UDP 기반 통신 | LiteNetLib으로 TCP 오버헤드 없이 실시간 통신 | | Protobuf 직렬화 | 컴팩트 바이너리 인코딩으로 대역폭 절약 | | 이동 패킷 Unreliable | HOL 블로킹 방지, 최신 위치만 유효 | | 싱글 스레드 이벤트 루프 | Lock 불필요, 동시성 이슈 최소화 | | 비동기 DB/API | async/await로 네트워크 루프 블로킹 방지 | | 크래시 덤프 | 프로덕션 환경 사후 분석 지원 | | 계층화된 DB 구조 | Handler-Service-Repository 패턴 | | Docker 배포 | 컨테이너화된 일관된 배포 환경 | ### 5.3 향후 작업 (Roadmap) | 우선순위 | 작업 | 설명 | |---------|------|------| | **높음** | 플레이어 DB 연동 | 캐릭터 정보 저장/불러오기 완성 | | **높음** | 서버 사이드 위치 검증 | 치트 방어를 위한 이동 유효성 검사 | | **중간** | NPC 시스템 | AI 기반 NPC 이동/전투 로직 | | **낮음** | 파티 시스템 | 파티 생성/참여/매칭 | | **낮음** | 모니터링 대시보드 | 서버 상태/접속자 수 실시간 모니터링 | > **완료된 항목**: 토큰 캐시 정리 (기 구현 확인), 패킷 레이트 리미팅 (적용 완료), 부하 테스트 도구 (구현 완료) ### 5.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 모드:** ```bash # 기본 (50명, 60초) dotnet run -- stress # 커스텀 dotnet run -- stress -c 100 -d 120 --ip 192.168.0.10 --port 9500 # 전체 옵션 dotnet run -- stress \ -c 200 # 클라이언트 수 -d 60 # 테스트 시간 (초, 0=무제한) -i 100 # 전송 주기 (ms) -r 1000 # Ramp-up 간격 (ms) -b 20 # Ramp-up 당 추가 수 --ip localhost # 서버 IP --port 9500 # 서버 포트 ``` **대화형 모드:** ``` 메뉴에서 3번 선택 → 클라이언트 수, 테스트 시간 입력 ``` #### 출력 예시 ``` ╔═══════════════════════════════════════════════╗ ║ STRESS TEST 최종 리포트 ║ ╠═══════════════════════════════════════════════╣ ║ 테스트 시간 : 60.0초 ║ 클라이언트 : 98/100 접속 유지 ╠═══════════════════════════════════════════════╣ ║ [처리량] ║ 총 전송 : 118,200 패킷 ║ 총 수신 : 117,850 패킷 ║ 처리량 : 1,970.0 패킷/초 ╠═══════════════════════════════════════════════╣ ║ [레이턴시] (RTT 샘플: 59,100개) ║ Min : 0.15 ms ║ Avg : 1.23 ms ║ P50 : 0.89 ms ║ P95 : 3.45 ms ║ P99 : 8.12 ms ║ Max : 25.67 ms ╚═══════════════════════════════════════════════╝ ``` ### 5.5 최근 개발 이력 | 커밋 | 내용 | |------|------| | `2be1302` | 크래시 덤프 기능 추가 (Release: 힙 덤프) | | `42f0ef1` | 이동 패킷 Unreliable 전송으로 변경 | | `bfa3394` | 토큰 → HashKey 생성 로직 구조 변경 | | `c8ce36a` | 로그인 실패 시 재시도 로직 추가 | | `18fd8a0` | 에코 클라이언트 수신 버그 수정 | --- > **작성일**: 2026-03-05 > **프로젝트 상태**: 핵심 네트워크 인프라 완성, 게임 콘텐츠 확장 단계