
고성능, 크로스 플랫폼 게임 서버 프레임워크
📖 문서 • 🚀 빠른 시작 • 💬 QQ 그룹: 467608841
🌐 언어: English | 简体中文 | 繁體中文 | 日本語 | 한국어
GameFrameX Server는 C# .NET 10.0으로 개발된 고성능, 크로스 플랫폼 게임 서버 프레임워크입니다. Actor 모델을 채택하고 핫 업데이트 메커니즘을 지원합니다. 멀티플레이어 온라인 게임 개발을 위해 설계되었으며 Unity3D, Godot, LayaBox 등 다양한 클라이언트 플랫폼 통합을 지원합니다.
설계 철학: 대도지간, 심플 이즈 베스트
┌─────────────────────────────────────────────────────────────────┐
│ 클라이언트 레이어 │
│ Unity3D / Godot / LayaBox / Cocos Creator │
├─────────────────────────────────────────────────────────────────┤
│ 네트워크 레이어 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ TCP │ │WebSocket │ │ HTTP │ │ KCP │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 메시지 처리 레이어 │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │TCP 메시지 │ │ HTTP 핸들러 │ │크로스 프로세스 │ │
│ │핸들러 │ │ │ │메시지 라우터 │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Actor 레이어 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 플레이어 │ │ 서버 │ │ 계정 │ │ 글로벌 │ │
│ │ Actor │ │ Actor │ │ Actor │ │ Actor │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 컴포넌트-에이전트 레이어(핫 업데이트 경계) │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ Apps 레이어 (핫불가) │ │ Hotfix 레이어 (핫 가능) │ │
│ │ StateComponent<T> │←→│ StateComponentAgent<T,TState>│ │
│ │ CacheState │ │ ComponentAgent │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 데이터베이스 레이어 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ MongoDB │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Server/
├── GameFrameX.Launcher/ # 애플리케이션 진입점
├── GameFrameX.StartUp/ # 시작 오케스트레이션 및 초기화
├── GameFrameX.Core/ # 코어 프레임워크(Actor 시스템, 컴포넌트, 이벤트, 핫 업데이트 관리)
├── GameFrameX.Apps/ # 상태 데이터 레이어(계정, 플레이어, 서버 모듈) — 핫 업데이트 불가
├── GameFrameX.Hotfix/ # 비즈니스 로직 레이어(HTTP, 플레이어, 서버 핸들러) — 핫 업데이트 가능
├── GameFrameX.Config/ # 게임 설정 테이블(JSON 형식, LuBan 생성)
├── GameFrameX.Core.Config/ # 코어 설정 관리
├── GameFrameX.Proto/ # ProtoBuf 프로토콜 정의
├── GameFrameX.ProtoBuf.Net/ # ProtoBuf 직렬화 구현
├── GameFrameX.NetWork/ # 네트워크 코어(메시지 객체, 센더, WebSocket)
├── GameFrameX.NetWork.Abstractions/ # 네트워크 인터페이스(IMessage, IMessageHandler, 메시지 매핑)
├── GameFrameX.NetWork.HTTP/ # HTTP 서버(Swagger, Kestrel, BaseHttpHandler)
├── GameFrameX.NetWork.Kcp/ # KCP 프로토콜 지원(UDP 기반 신뢰성 전송)
├── GameFrameX.NetWork.Message/ # 메시지 파이프라인 및 코덱
├── GameFrameX.NetWork.RemoteMessaging/ # 크로스 프로세스 원격 메시지(서킷 브레이커, 재시도, 일관 해싱)
├── GameFrameX.DataBase/ # 데이터베이스 추상 레이어
├── GameFrameX.DataBase.Mongo/ # MongoDB 구현(헬스 모니터링, 재시도, 배치 작업)
├── GameFrameX.Localization/ # 현지화 시스템(Keys.*.cs + .resx 리소스 파일)
├── GameFrameX.Monitor/ # OpenTelemetry + Prometheus 메트릭 통합
├── GameFrameX.Utility/ # 유틸리티(로깅, 압축, 오브젝트 풀, Mapster, Harmony)
├── GameFrameX.Client/ # 테스트 클라이언트(TCP 연결)
├── GameFrameX.CodeGenerator/ # Roslyn 소스 제너레이터(핫 업데이트 프록시 래퍼 클래스)
├── GameFrameX.AppHost/ # .NET Aspire 애플리케이션 호스트
├── GameFrameX.AppHost.ServiceDefaults/ # Aspire 공유 기본 설정(OTel, 서비스 디스커버리)
└── Tests/
└── GameFrameX.Tests/ # xUnit 테스트 스위트
저장소 클론
git clone https://github.com/GameFrameX/GameFrameX.git
cd GameFrameX/Server
종속성 복원
dotnet restore
프로젝트 빌드
dotnet build
MongoDB 시작
# 로컬 설치
mongod --dbpath /path/to/data
# 또는 Docker 사용
docker run -d -p 27017:27017 --name mongo mongo:8.2
서버 실행
dotnet run --project GameFrameX.Launcher -- \
--ServerType=Game \
--ServerId=1000 \
--OuterPort=29100 \
--HttpPort=28080 \
--DataBaseUrl=mongodb://127.0.0.1:27017 \
--DataBaseName=gameframex
시작 확인
http://localhost:28080/game/api/healthGameFrameX는 명령줄 인수(--Key=Value)로 설정합니다. 모든 설정 항목은 StartupOptions 클래스에 정의되어 있습니다.
| 설정 항목 | 설명 | 기본값 | 예시 |
|---|---|---|---|
ServerType | 서버 유형(필수) | 없음 | Game, Social |
ServerId | 서버 고유 ID | 없음 | 1000 |
ServerInstanceId | 서버 인스턴스 ID(동일 유형의 다른 인스턴스 구분) | 0 | 1001 |
IsSingleMode | 단일 프로세스 모드 여부 | false | true |
MinModuleId | 비즈니스 모듈 시작 ID(모듈 샤딩) | 0 | 100 |
MaxModuleId | 비즈니스 모듈 종료 ID(모듈 샤딩) | 0 | 1000 |
TimeZone | 서버 시간대 | Asia/Shanghai | UTC |
IsUseTimeZone | 커스텀 시간대 활성화 | false | true |
Language | 언어 설정 | 없음 | zh-CN |
| 설정 항목 | 설명 | 기본값 | 예시 |
|---|---|---|---|
InnerHost | 내부 통신 IP(클러스터 간) | 0.0.0.0 | 0.0.0.0 |
InnerPort | 내부 통신 포트 | 8888 | 29100 |
OuterHost | 외부 통신 IP(클라이언트 대상) | 0.0.0.0 | 0.0.0.0 |
OuterPort | 외부 통신 포트 | 없음 | 29100 |
IsEnableTcp | TCP 서비스 활성화 | true | true |
IsEnableUdp | UDP 서비스 활성화 | false | true |
IsEnableWebSocket | WebSocket 활성화 | false | true |
WsPort | WebSocket 포트 | 8889 | 29300 |
IsEnableHttp | HTTP 서비스 활성화 | true | true |
HttpPort | HTTP 서비스 포트 | 8080 | 28080 |
HttpsPort | HTTPS 서비스 포트 | 없음 | 443 |
HttpUrl | API 루트 경로 | /game/api/ | /game/api/ |
HttpIsDevelopment | HTTP 개발 모드(Swagger 활성화) | false | true |
| 설정 항목 | 설명 | 기본값 | 예시 |
|---|---|---|---|
DataBaseUrl | MongoDB 연결 문자열 | 없음 | mongodb://localhost:27017 |
DataBaseName | 데이터베이스 이름 | 없음 | gameframex |
DataBasePassword | 데이터베이스 비밀번호 | 없음 | your_password |
| 설정 항목 | 설명 | 기본값 | 예시 |
|---|---|---|---|
ActorTimeOut | Actor 작업 실행 타임아웃(밀리초) | 30000 | 60000 |
ActorQueueTimeOut | Actor 큐 타임아웃(밀리초) | 30000 | 60000 |
ActorRecycleTime | Actor 유휴 리사이클 시간(분) | 15 | 30 |
SaveDataInterval | 데이터 저장 간격(밀리초) | 30000 | 60000 |
SaveDataBatchCount | 배치 저장 수 | 500 | 1000 |
SaveDataBatchTimeOut | 배치 저장 타임아웃(밀리초) | 30000 | 60000 |
| 설정 항목 | 설명 | 기본값 | 예시 |
|---|---|---|---|
IsDebug | 디버그 로깅 마스터 스위치 | false | true |
LogIsConsole | 콘솔 출력 | true | false |
LogIsWriteToFile | 파일 출력 | true | false |
LogEventLevel | 로그 레벨 | Debug | Information |
LogRollingInterval | 로그 롤링 간격 | Day | Hour |
LogIsFileSizeLimit | 단일 파일 크기 제한 | true | false |
LogFileSizeLimitBytes | 파일 크기 제한 | 104857600 (100MB) | 52428800 |
LogRetainedFileCountLimit | 보관 파일 수 | 31 | 90 |
LogIsGrafanaLoki | Grafana Loki 출력 | false | true |
LogGrafanaLokiUrl | Grafana Loki URL | http://localhost:3100 | — |
| 설정 항목 | 설명 | 기본값 | 예시 |
|---|---|---|---|
IsOpenTelemetry | OpenTelemetry 활성화 | false | true |
IsOpenTelemetryMetrics | 메트릭 수집 활성화 | false | true |
IsOpenTelemetryTracing | 분산 트레이싱 활성화 | false | true |
MetricsPort | Prometheus 메트릭 포트 | 0(HTTP 포트 공유) | 9090 |
IsMonitorMessageTimeOut | 메시지 처리 타임아웃 모니터링 | false | true |
MonitorMessageTimeOutSeconds | 타임아웃 임계값(초) | 1 | 5 |
| 설정 항목 | 설명 | 기본값 | 예시 |
|---|---|---|---|
WorkerId | 스노우플레이크 ID 워커 노드 ID | 1 | 2 |
DataCenterId | 스노우플레이크 ID 데이터센터 ID | 1 | 2 |
# 최소 시작 매개변수
dotnet GameFrameX.Launcher.dll \
--ServerType=Game \
--ServerId=1000 \
--DataBaseUrl=mongodb://127.0.0.1:27017 \
--DataBaseName=game_db
# 전체 시작 매개변수
dotnet GameFrameX.Launcher.dll \
--ServerType=Game \
--ServerId=1000 \
--ServerInstanceId=1 \
--InnerHost=0.0.0.0 \
--InnerPort=29100 \
--OuterHost=0.0.0.0 \
--OuterPort=29100 \
--HttpPort=28080 \
--IsEnableHttp=true \
--HttpIsDevelopment=true \
--IsEnableWebSocket=false \
--DataBaseUrl=mongodb://127.0.0.1:27017 \
--DataBaseName=gameframex \
--IsDebug=true \
--IsOpenTelemetry=true \
--IsOpenTelemetryMetrics=true \
--LogIsConsole=true \
--LogIsWriteToFile=true
프레임워크의 핵심 설계 패턴은 상태-로직 분리입니다. 영속화 상태(Apps 레이어, 핫 업데이트 불가)와 비즈니스 로직(Hotfix 레이어, 핫 업데이트 가능)을 엄격히 분리합니다.
1. 상태 정의(Apps 레이어)
// GameFrameX.Apps/Player/BagState.cs
public class BagState : BaseCacheState
{
public List<ItemData> Items { get; set; } = new List<ItemData>();
public int MaxSlots { get; set; } = 50;
}
2. 컴포넌트 생성(Apps 레이어)
// GameFrameX.Apps/Player/BagComponent.cs
public class BagComponent : StateComponent<BagState>
{
protected override async Task OnInit()
{
await base.OnInit();
// 컴포넌트 상태 초기화
}
}
3. 비즈니스 로직 구현(Hotfix 레이어)
// GameFrameX.Hotfix/Logic/Player/BagComponentAgent.cs
public class BagComponentAgent : StateComponentAgent<BagComponent, BagState>
{
public async Task<bool> AddItem(int itemId, int count)
{
if (State.Items.Count >= State.MaxSlots)
{
return false;
}
var item = new ItemData { Id = itemId, Count = count };
State.Items.Add(item);
await Save();
return true;
}
}
4. 컴포넌트 에이전트 접근
// ActorManager를 통해 컴포넌트 에이전트 가져오기
var bagAgent = await ActorManager.GetComponentAgent<BagComponentAgent>(playerId);
var result = await bagAgent.AddItem(1001, 10);
HTTP 핸들러는 BaseHttpHandler를 상속하고 [HttpMessageMapping] 속성으로 라우트를 등록합니다.
[HttpMessageMapping(typeof(GetPlayerInfoHandler))]
[Description("플레이어 정보 가져오기")]
public sealed class GetPlayerInfoHandler : BaseHttpHandler
{
public override async Task<MessageObject> Action(
string ip, string url,
Dictionary<string, object> parameters,
MessageObject messageObject)
{
var request = (GetPlayerInfoRequest)messageObject;
var response = new GetPlayerInfoResponse();
var agent = await ActorManager.GetComponentAgent<PlayerComponentAgent>(request.PlayerId);
if (agent == null)
{
response.ErrorCode = (int)ResultCode.PlayerNotFound;
return response;
}
response.PlayerInfo = await agent.GetPlayerInfo();
return response;
}
}
TCP 메시지 핸들러는 클라이언트가 TCP 연결을 통해 보낸 게임 메시지를 처리합니다.
단방향 메시지 핸들러:
[MessageMapping(typeof(ReqChatMessage))]
internal sealed class ChatMessageHandler : PlayerComponentHandler<ChatComponentAgent, ReqChatMessage>
{
protected override async Task ActionAsync(ReqChatMessage request)
{
await ComponentAgent.ProcessChatMessage(request);
}
}
RPC 핸들러(요청-응답):
[MessageMapping(typeof(ReqAddItem))]
internal sealed class AddItemHandler : PlayerRpcComponentHandler<BagComponentAgent, ReqAddItem, RespAddItem>
{
protected override async Task ActionAsync(ReqAddItem request, RespAddItem response)
{
try
{
// ComponentAgent는 기본 클래스에서 자동 주입
await ComponentAgent.AddItem(request, response);
}
catch (Exception e)
{
LogHelper.Fatal(e);
response.ErrorCode = (int)OperationStatusCode.InternalServerError;
}
}
}
이벤트 시스템은 Actor 간의 느슨한 결합 통신에 사용됩니다.
[Event(EventId.PlayerLogin)]
internal sealed class PlayerLoginEventHandler : EventListener<PlayerComponentAgent>
{
protected override Task HandleEvent(PlayerComponentAgent agent, GameEventArgs gameEventArgs)
{
if (agent == null)
{
return Task.CompletedTask;
}
// 플레이어 로그인 이벤트 처리
return agent.OnLogin();
}
}
핫 업데이트 시스템은 AssemblyLoadContext(수집 가능)를 통해 어셈블리의 런타임 로드 및 언로드를 구현합니다:
┌───────────────────────────────────────────────────────┐
│ Apps 레이어(핫 업데이트 불가) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │StateComponent│ │StateComponent│ │StateComponent│ │
│ │ 영속화 상태 │ │ 영속화 상태 │ │ 영속화 상태 │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
├─────────┼────────────────┼────────────────┼───────────┤
│ ▼ ▼ ▼ │
│ Hotfix 레이어(핫 업데이트 가능) — AssemblyLoadContext │
│ 를 통해 로드 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ComponentAgent│ │ComponentAgent│ │ComponentAgent│ │
│ │ 비즈니스 로직 │ │ 비즈니스 로직 │ │ 비즈니스 로직 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Msg Handler │ │ EventHandler│ │ HttpHandler │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└───────────────────────────────────────────────────────┘
GameFrameX.Hotfix.dll 빌드HotfixManager가 수집 가능한 AssemblyLoadContext로 새 DLL 로드HotfixModule이 새 어셈블리에서 에이전트, 핸들러, 이벤트 리스너를 스캔ActorManager.ClearAgent()가 캐시된 에이전트 인스턴스를 클리어# 핫 업데이트 트리거(버전 지정)
curl -X POST "http://localhost:28080/game/api/Reload?version=1.7.2"
docker-compose.yml을 사용하여 MongoDB + Game + Social의 완전한 환경을 시작:
# 빌드 및 시작
docker compose up -d --build
# 실행 상태 확인
docker compose ps
# 로그 확인
docker compose logs -f game social
# 중지
docker compose down
서비스 포트 매핑:
| 서비스 | 컨테이너 포트 | 호스트 포트 | 설명 |
|---|---|---|---|
| MongoDB | 27017 | 37017 | 데이터베이스 |
| Game TCP | 29100 | 39100 | 게임 서버 |
| Game HTTP | 28080 | 38080 | 게임 서버 HTTP API |
| Social TCP | 29400 | 39400 | 소셜 서버 |
| Social HTTP | 28081 | 38081 | 소셜 서버 HTTP API |
docker-compose.multi.yml을 사용하여 1 MongoDB + 2 Social + 10 Game의 클러스터 환경을 시작:
# 빌드 및 시작
docker compose -f docker-compose.multi.yml up -d --build
# 실행 상태 확인
docker compose -f docker-compose.multi.yml ps
# 중지
docker compose -f docker-compose.multi.yml down
클러스터 토폴로지:
| 컴포넌트 | 인스턴스 수 | 설명 |
|---|---|---|
| MongoDB | 1 | 공유 데이터베이스 |
| Social | 2 | 소셜 서버(social-1, social-2) |
| Game | 10 | 게임 서버(game-1 ~ game-10) |
모든 인스턴스는 Aspire 스타일 환경 변수로 서비스 디스커버리를 수행합니다:
environment:
services__Social_2001__tcp__0: "tcp://social-1:29400"
services__Social_2002__tcp__0: "tcp://social-2:29401"
services__Game_1001__tcp__0: "tcp://game-1:29100"
# ...
# 이미지 빌드
docker build -t gameframex/server:custom .
# 실행
docker run -d \
--name my-game-server \
-p 29100:29100 \
-p 28080:28080 \
gameframex/server:custom \
--ServerType=Game \
--ServerId=2000 \
--DataBaseUrl=mongodb://mongo-host:27017 \
--DataBaseName=my_game
# 멀티 인스턴스 환경이 실행 중인지 확인
docker compose -f docker-compose.multi.yml up -d --build
# 크로스 프로세스 스모크 테스트 실행
./scripts/multi/smoke-cross-process.sh
스크립트 검증 내용:
game-1 → social 크로스 프로세스 호출game-2 → social 크로스 프로세스 호출code=0 및 FriendCount >= 1 반환실제 클라이언트를 시뮬레이션하여 "로그인 → 온라인 → 능동적 연결 해제 → 재연결 로그인"을 반복:
# 기본 매개변수로 실행
./scripts/multi/run-bots-rpc.sh
# 커스텀 매개변수
BOT_COUNT=200 \
TCP_PORT=49100 \
LOGIN_URL=http://127.0.0.1:48080/game/api/ \
DISCONNECT_AFTER_LOGIN_SECONDS=20 \
RUN_SECONDS=300 \
./scripts/multi/run-bots-rpc.sh
선택적 환경 변수:
| 변수 | 설명 | 기본값 |
|---|---|---|
BOT_COUNT | 봇 수 | — |
TCP_PORT | TCP 연결 포트 | 49100 |
LOGIN_URL | 로그인 API URL | http://127.0.0.1:48080/game/api/ |
DISCONNECT_AFTER_LOGIN_SECONDS | 로그인 후 연결 해제 지연(초) | 20 |
RUN_SECONDS | 총 실행 시간(초) | 300 |
# 모든 서비스 로그 확인
docker compose -f docker-compose.multi.yml logs -f
# 특정 서비스 로그 확인
docker compose -f docker-compose.multi.yml logs -f game-1 game-2 social-1 social-2
# 리빌드 및 시작(코드 변경 후)
docker compose -f docker-compose.multi.yml up -d --build
| 엔드포인트 | 설명 |
|---|---|
http://<host>:<HttpPort>/game/api/health | 헬스 체크 |
http://<host>:<MetricsPort>/metrics | Prometheus 메트릭 |
db_operation_latency_ms), 재시도 횟수(db_open_retry_total), 헬스 상태(db_health_status)# 모든 테스트 실행
dotnet test
# 특정 테스트 프로젝트 실행
dotnet test Tests/GameFrameX.Tests/GameFrameX.Tests.csproj
# 상세 출력으로 실행
dotnet test --logger "console;verbosity=detailed"
테스트 프로젝트는 xUnit 기반이며, 다음 모듈을 커버합니다:
| 테스트 디렉토리 | 설명 |
|---|---|
Utility/ | 수학/고정소수점 테스트, 압축, 난수, ID 생성, 싱글톤 |
NetWork/Kcp/ | KCP 파이프라인 필터, 세션 관리, 서버 통합 테스트 |
DataBase/ | MongoDB 연결 및 쿼리 테스트 |
ProtoBuff/ | Protobuf 직렬화 및 오브젝트 풀 테스트 |
Localization/ | 현지화 키값 파싱 테스트 |
RemoteMessaging/ | 크로스 프로세스 메시징 테스트 |
UnifiedMessaging/ | 통합 크로스 프로세스 메시징 테스트 |
StartUp/ | HTTP 서버 라우트 등록 테스트 |
모든 형태의 기여를 환영합니다! 다음 단계를 따라주세요:
git checkout -b feature/amazing-feature)git commit -m 'feat: 기능 추가')git push origin feature/amazing-feature)커밋 메시지는 Conventional Commits 사양을 따라주세요.
본 프로젝트는 MIT 라이선스와 Apache License 2.0의 듀얼 라이선스로 배포됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.
이 프로젝트가 도움이 되었다면 Star를 부탁드립니다
Made by GameFrameX Team