Files

597 lines
28 KiB
Markdown

# 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<int, NetPeer> Dictionary<long, NetPeer>
접속 시 등록 ──────────────→ 인증 성공 시 이동
(peerId 기반) (hashKey 기반)
TokenHash (토큰 매핑)
Dictionary<string, long>
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 버그 목록
#### [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` 발생 가능
```csharp
public void AddUser(int channelId, long userId, Player player)
{
connectUsers.Add(userId, channelId); // 중복 키 시 예외 발생
channels[channelId].AddUser(userId, player);
}
```
- **영향**: 동시 재접속 시나리오에서 서버 크래시 가능성
- **권장 조치**: `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 아키텍처 요약
```
┌──────────────────────────────────────────────────────┐
│ 클라이언트 (Unity) │
└──────────────┬───────────────────────────┬────────────┘
│ UDP (LiteNetLib) │
┌──────────────▼───────────────────────────▼────────────┐
│ ServerBase (네트워크 코어) │
│ ┌────────────┐ ┌────────────┐ ┌─────────────────┐ │
│ │ 연결 관리 │ │ 패킷 직렬화 │ │ 세션 관리 │ │
│ └────────────┘ └────────────┘ └─────────────────┘ │
└──────────────┬───────────────────────────┬────────────┘
│ │
┌──────────────▼───────────────┐ ┌────────▼────────────┐
│ GameServer (게임 로직) │ │ RestApi (인증) │
│ ┌─────────┐ ┌────────────┐ │ │ JWT 토큰 검증 │
│ │ Channel │ │ Player │ │ │ (외부 API 호출) │
│ │ Manager │ │ State │ │ └─────────────────────┘
│ └─────────┘ └────────────┘ │
└──────────────┬───────────────┘
┌──────────────▼───────────────┐
│ RDB Layer (데이터베이스) │
│ Handler → Service → Repo │
│ → MySQL │
└──────────────────────────────┘
```
### 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 모드:**
```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
╚═══════════════════════════════════════════════╝
```
### 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 취약점