인디 게임 개발자를 위한 올인원 솔루션 · 인디 개발자의 꿈을 실현
📖 문서 · 🚀 빠른 시작 · 💬 QQ 그룹: 467608841
🌐 언어: English | 简体中文 | 繁體中文 | 日本語 | 한국어
GameFrameX Server는 C# .NET 10.0 기반의 고성능 게임 서버 프레임워크로, Actor 모델 아키텍처와 핫 업데이트를 지원합니다. 이 프레임워크는 영속 상태와 비즈니스 로직의 엄격한 분리를 적용하며, 멀티플레이어 온라인 게임 시나리오에 특별히 설계되었습니다.
설계 철학: 복잡함보다는 단순함
- Actor 모델 — TPL DataFlow 기반의 락-프리 고동시성 아키텍처로, 메시지 전달을 통해 전통적인 락 경합을 방지합니다
- 상태-로직 분리 — 영속 데이터(Apps 레이어)와 핫 업데이트 가능한 비즈니스 로직(Hotfix 레이어)의 엄격한 분리
- 무중단 핫 업데이트 — 서버 재시작 없이 런타임에 비즈니스 로직 어셈블리 교체 가능
- 멀티 프로토콜 네트워킹 — TCP, WebSocket, HTTP 프로토콜 지원, 내장 메시지 인코딩/디코딩 및 압축 기능
- MongoDB 영속화 — CacheState 기반 투명 ORM 매핑으로 자동 직렬화/역직렬화 지원
- 소스 제너레이터 — Roslyn 기반 Agent 코드 생성으로 자동 Actor 메시지 큐 스케줄링
- 설정 테이블 시스템 — Luban 설정 생성 통합 및 JSON 핫 리로딩 지원
- OpenTelemetry — 내장 Prometheus 메트릭 내보내기, 헬스 체크 및 성능 모니터링
┌──────────────────────────────────────────────────────────────┐
│ Client Layer │
├──────────────────────────────────────────────────────────────┤
│ Network Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ TCP │ │WebSocket │ │ HTTP │ │ KCP │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Hotfix Layer (Hot-updatable) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ComponentAgent │ HttpHandler │ EventHandler │ │
│ └──────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Apps Layer (Non-hot-updatable) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ State │ │Component │ │ActorType │ │
│ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Framework Layer (NuGet Packages) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Core │ │ NetWork │ │ DataBase │ │ Monitor │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Database Layer │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ MongoDB │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
| 종속성 | 버전 |
|---|---|
| .NET SDK | 10.0+ |
| MongoDB | 4.x+ |
| IDE | Visual Studio 2022 / JetBrains Rider / VS Code |
# 1. Clone the repository
git clone https://github.com/GameFrameX/Server_main.git
cd Server_main
# 2. Restore dependencies
dotnet restore
# 3. Build
dotnet build
# 4. Run the game server
dotnet run --project GameFrameX.Launcher \
--ServerType=Game \
--ServerId=1000 \
--APMPort=29090| 엔드포인트 | URL | 설명 |
|---|---|---|
| 헬스 체크 | http://localhost:29090/health |
서비스 상태 확인 |
| 메트릭 | http://localhost:29090/metrics |
Prometheus 메트릭 |
| 테스트 API | http://localhost:28080/game/api/test |
HTTP 연결 테스트 |
Server_main/
├── GameFrameX.Launcher/ # 애플리케이션 진입점 (실행 파일)
│ ├── Program.cs # 부트스트랩: 상태 타입 및 프로토콜 메시지 등록
│ └── StartUp/
│ └── AppStartUpGame.cs # 게임 서버 시작 흐름
│
├── GameFrameX.Hotfix/ # 핫 업데이트 레이어 (비즈니스 로직, 런타임 교체 가능)
│ ├── Logic/
│ │ ├── Http/ # HTTP 요청 핸들러
│ │ │ ├── TestHttpHandler.cs
│ │ │ ├── ReloadHttpHandler.cs
│ │ │ ├── Player/ # 계정 로그인, 온라인 조회
│ │ │ └── Bag/ # 아이템 발송
│ │ ├── Player/
│ │ │ ├── Bag/ # 가방 컴포넌트 에이전트
│ │ │ ├── Login/ # 로그인/로그아웃 로직
│ │ │ └── Pet/ # 펫 컴포넌트 에이전트
│ │ ├── Account/ # 계정 컴포넌트 에이전트
│ │ └── Server/ # 서버 글로벌 컴포넌트 에이전트
│ └── StartUp/ # 핫 업데이트 시작 흐름
│ ├── AppStartUpHotfixGameByEntry.cs # 진입점
│ ├── AppStartUpHotfixGameByMain.cs # 네트워크/연결 관리
│ ├── AppStartUpHotfixGameByHeart.cs # 하트비트 처리
│ └── AppStartUpHotfixGameByGateWay.cs # 게이트웨이 통신
│
├── GameFrameX.Apps/ # 애플리케이션 상태 레이어 (핫 업데이트 불가)
│ ├── ActorType.cs # Actor 타입 열거형 정의
│ ├── Account/Login/ # 로그인 상태 + 컴포넌트
│ ├── Player/
│ │ ├── Bag/ # 가방 상태 + 컴포넌트
│ │ ├── Player/ # 플레이어 상태 + 컴포넌트
│ │ └── Pet/ # 펫 상태 + 컴포넌트
│ ├── Server/ # 서버 글로벌 상태 + 컴포넌트
│ └── Common/
│ ├── Session/ # 세션 관리 (SessionManager)
│ ├── Event/ # 이벤트 ID 정의
│ └── EventData/ # 이벤트 인자
│
├── GameFrameX.Config/ # 설정 테이블 시스템 (Luban 생성)
│ ├── ConfigComponent.cs # 설정 로딩 싱글톤
│ ├── Tables/ # 생성된 설정 테이블 클래스
│ ├── TablesItem/ # 설정 데이터 모델
│ └── json/ # JSON 설정 데이터 파일
│
├── GameFrameX.Proto/ # 네트워크 프로토콜 정의
│ ├── Basic_10.cs # 기본 프로토콜 (하트비트 등)
│ ├── Bag_100.cs # 가방 프로토콜
│ ├── User_300.cs # 사용자/계정 프로토콜
│ └── BuiltIn/ # 내장 시스템 프로토콜
│
├── GameFrameX.CodeGenerator/ # Roslyn 소스 제너레이터
│ ├── AgentGenerator.cs # Agent 래퍼 코드 생성
│ └── AgentTemplate.cs # 코드 템플릿
│
├── Server.sln # Visual Studio 솔루션
├── Dockerfile # Docker 다단계 빌드
├── docker-compose.yml # Docker Compose 오케스트레이션
└── LICENSE # MIT + Apache 2.0 듀얼 라이선스
GameFrameX는 서버 무중단으로 비즈니스 로직을 핫 업데이트할 수 있는 3계층 분리 패턴을 사용합니다.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ State │────▶│ Component │◀────│ ComponentAgent│
│ (Data Def) │ │ (Shell) │ │ (Logic) │
│ Apps Layer │ │ Apps Layer │ │ Hotfix Layer │
│ Not Hot-fix │ │ Not Hot-fix │ │ Hot-fixable │
└──────────────┘ └──────────────┘ └──────────────┘
CacheState를 상속하여 영속 데이터 구조를 정의합니다. 프레임워크가 MongoDB 직렬화를 자동으로 처리합니다.
// GameFrameX.Apps/Player/Bag/Entity/BagState.cs
public sealed class BagState : CacheState
{
public Dictionary<int, BagItemState> List { get; set; } = new();
}
public sealed class BagItemState
{
public long ItemId { get; set; }
public long Count { get; set; }
}StateComponent<TState>를 상속하여 상태와 로직 간의 브릿지 역할을 합니다.
// GameFrameX.Apps/Player/Bag/Component/BagComponent.cs
[ComponentType(GlobalConst.ActorTypePlayer)]
public class BagComponent : StateComponent<BagState> { }StateComponentAgent<TComponent, TState>를 상속하여 핫 업데이트 가능한 비즈니스 코드를 작성합니다.
// GameFrameX.Hotfix/Logic/Player/Bag/BagComponentAgent.cs
public class BagComponentAgent : StateComponentAgent<BagComponent, BagState>
{
public async Task OnAddBagItem(INetWorkChannel netWorkChannel,
ReqAddItem message, RespAddItem response)
{
// Validate item config exists
foreach (var item in message.ItemDic)
{
if (!ConfigComponent.Instance.GetConfig<TbItemConfig>()
.TryGet(item.Key, out var _))
{
response.ErrorCode = (int)OperationStatusCode.NotFound;
return;
}
}
await UpdateChanged(netWorkChannel, message.ItemDic);
}
public async Task<BagState> UpdateChanged(INetWorkChannel netWorkChannel,
Dictionary<int, long> itemDic)
{
var bagState = OwnerComponent.State;
var notify = new NotifyBagInfoChanged();
foreach (var item in itemDic)
{
if (bagState.List.TryGetValue(item.Key, out var value))
{
value.Count += item.Value;
}
else
{
bagState.List[item.Key] = new BagItemState
{
Count = item.Value, ItemId = item.Key
};
}
}
await netWorkChannel.WriteAsync(notify);
await OwnerComponent.WriteStateAsync(); // Auto-persist to MongoDB
return bagState;
}
}BaseHttpHandler를 상속하고 [HttpMessageMapping]으로 등록합니다.
// GameFrameX.Hotfix/Logic/Http/TestHttpHandler.cs
[HttpMessageMapping(typeof(TestHttpHandler))]
[HttpMessageResponse(typeof(HttpTestResponse))]
[Description("Test API endpoint")]
public sealed class TestHttpHandler : BaseHttpHandler
{
public override Task<string> Action(string ip, string url,
Dictionary<string, object> parameters)
{
var response = new HttpTestResponse { Message = "hello" };
return Task.FromResult(HttpJsonResult.SuccessString(response));
}
}프레임워크는 자동 ComponentAgent 주입이 포함된 두 가지 RPC 핸들러 기본 클래스를 제공합니다:
// Player-level message (bound to a specific player Actor)
[MessageMapping(typeof(ReqAddItem))]
internal sealed class AddItemHandler
: PlayerRpcComponentHandler<BagComponentAgent, ReqAddItem, RespAddItem>
{
protected override async Task ActionAsync(ReqAddItem request, RespAddItem response)
{
await ComponentAgent.OnAddBagItem(NetWorkChannel, request, response);
}
}
// Global-level message (bound to the server Actor)
[MessageMapping(typeof(ReqHeartBeat))]
internal sealed class HeartBeatHandler
: GlobalRpcComponentHandler<ServerComponentAgent, ReqHeartBeat, RespHeartBeat>
{
protected override Task ActionAsync(ReqHeartBeat request, RespHeartBeat response)
{
return Task.CompletedTask;
}
}[Event] 속성을 사용하여 이벤트 ID를 바인딩합니다. 프레임워크가 자동으로 해당 ComponentAgent에 디스패치합니다.
[Event(EventId.PlayerLogin)]
internal sealed class PlayerLoginEventHandler : EventListener<PlayerComponentAgent>
{
protected override Task HandleEvent(PlayerComponentAgent agent, GameEventArgs args)
{
return agent.OnLogin();
}
}Roslyn 소스 제너레이터가 Actor 메시지 큐 스케줄링을 자동으로 처리합니다. 속성을 통해 호출 동작을 제어합니다:
| 속성 | 설명 | 사용 사례 |
|---|---|---|
[Service] |
기본 모드; 메서드 호출이 Actor 메시지 큐에 enqueue됩니다 | 모든 비즈니스 메서드 |
[ThreadSafe] |
메시지 큐를 건너뛰고 직접 호출합니다 (스레드 안전 구현 필요) | 읽기 전용 작업, 무상태 연산 |
[Discard] |
실행 후 결과를 기다리지 않습니다 | 로깅, 통계 등 결과가 필요 없는 작업 |
[TimeOut(ms)] |
메시지 큐 호출에 대한 타임아웃을 설정합니다 | 타임아웃 제어가 필요한 긴 작업 |
public class ServerComponentAgent : StateComponentAgent<ServerComponent, ServerState>
{
// Enqueued to Actor message queue
[Service]
public virtual Task<bool> IsOnline(long roleId) { ... }
// Skip message queue, thread-safe direct call
[Service]
[ThreadSafe]
public virtual long FirstStartTime()
{
return State.FirstStartTime;
}
// Fire-and-forget, non-blocking
[Service]
[Discard]
public virtual ValueTask AddOnlineRole(long roleId)
{
OwnerComponent.OnlineSet.Add(roleId);
return ValueTask.CompletedTask;
}
}ConfigComponent 싱글톤을 사용하여 Luban 생성 설정 테이블에 접근합니다:
var config = ConfigComponent.Instance.GetConfig<TbItemConfig>();
// Safe query with TryGet
if (config.TryGet(itemId, out var itemConfig))
{
// Use itemConfig.Name, itemConfig.Type, etc.
}GameDb 정적 클래스를 사용하여 MongoDB CRUD 작업을 수행합니다:
// Query
var state = await GameDb.FindAsync<LoginState>(
m => m.UserName == userName && m.Password == password);
// Add or update
await GameDb.AddOrUpdateAsync(loginState);
// List query
var list = await GameDb.FindListAsync<LoginState>(m => m.Id != 0);
// Delete
var count = await GameDb.DeleteAsync(state);핫 업데이트 시스템은 서버를 중지하지 않고 비즈니스 로직을 교체할 수 있습니다.
- Apps 레이어 (
GameFrameX.Apps): 상태 정의와 컴포넌트 셸을 포함 — 핫 업데이트 불가 - Hotfix 레이어 (
GameFrameX.Hotfix): 모든 비즈니스 로직을 포함 — 핫 업데이트 가능 - Hotfix 어셈블리는
hotfix/디렉토리에 출력되며, 런타임에HotfixManager에 의해 로드됩니다
# Trigger via HTTP endpoint (with version number)
curl "http://localhost:28080/game/api/Reload?version=1.0.1"| 매개변수 | 설명 | 기본값 | 예시 |
|---|---|---|---|
ServerType |
서버 타입 | — | Game |
ServerId |
고유 서버 ID | — | 1000 |
InnerPort |
TCP 내부 포트 | — | 29100 |
HttpPort |
HTTP 서비스 포트 | 0 |
28080 |
WsPort |
WebSocket 서비스 포트 | 0 |
29110 |
MetricsPort |
Prometheus 메트릭 포트 | 0 |
29090 |
DataBaseUrl |
MongoDB 연결 문자열 | — | mongodb://localhost:27017 |
DataBaseName |
데이터베이스 이름 | — | gameframex |
dotnet GameFrameX.Launcher.dll \
--ServerType=Game \
--ServerId=1000 \
--InnerPort=29100 \
--HttpPort=28080 \
--WsPort=29110 \
--MetricsPort=29090 \
--DataBaseUrl=mongodb://127.0.0.1:27017 \
--DataBaseName=game_dbdocker-compose up --build| 포트 | 프로토콜 | 설명 |
|---|---|---|
29090 |
HTTP | APM 메트릭 / 헬스 체크 |
29100 |
TCP | 게임 클라이언트 연결 |
29110 |
WebSocket | WebSocket 연결 |
28080 |
HTTP | HTTP API |
메시지 ID는 모듈 ID 시프트로 계산됩니다: (moduleId << 16) + seqId
| 모듈 | ID 범위 | 파일 | 설명 |
|---|---|---|---|
| 시스템 | -10 ~ -1 | Player_-10.cs, Service_-3.cs |
내장 시스템 프로토콜 |
| 기본 | 10 | Basic_10.cs |
하트비트, 서버 준비 알림 |
| 가방 | 100 | Bag_100.cs |
아이템 CRUD, 사용, 합성 |
| 사용자 | 300 | User_300.cs |
로그인, 회원가입, 캐릭터 목록 |
| 구성 요소 | 기술 |
|---|---|
| 런타임 | .NET 10.0 |
| 데이터베이스 | MongoDB |
| 네트워킹 | SuperSocket |
| 직렬화 | protobuf-net |
| 설정 생성 | Luban |
| 코드 생성 | Roslyn Source Generator |
| 모니터링 | OpenTelemetry + Prometheus |
| 객체 매핑 | Mapster |
| 컨테이너화 | Docker + Docker Compose |
- 이 저장소를 Fork합니다
- 기능 브랜치를 생성합니다 (
git checkout -b feature/amazing-feature) - 변경 사항을 커밋합니다 (
git commit -m 'feat: add some feature') - 브랜치에 푸시합니다 (
git push origin feature/amazing-feature) - Pull Request를 생성합니다
이 프로젝트는 MIT 라이선스와 Apache 라이선스 2.0의 듀얼 라이선스를 따릅니다. 자세한 내용은 LICENSE 파일을 참조하십시오.
