跳到主要内容

集成

1. 概述

本文介绍需要与 DS 托管服务交互的游戏模块,以及如何将这些游戏模块与 PGOS DS 托管服务集成。

1.1 主要组件

  • 游戏服务器 (DS) 是在 Fleet 上运行的服务端进程。游戏服务器进程将维护一个战斗会话并接受来自游戏客户端的连接请求。
  • 游戏客户端是在玩家设备上运行的进程。如果您需要通过 PGOS 战斗服务(匹配和房间)请求 DS 资源,游戏客户端可以使用 PGOS Client SDK 与 PGOS 进行通信。
  • 游戏后端服务是处理与 PGOS 之间请求和事件的自定义服务。如果您的游戏客户端未集成 PGOS Client SDK,我们建议后端服务中继所有游戏客户端与 PGOS 之间的通信。
  • 战斗会话是在游戏服务器上运行的游戏。战斗会话可以由 PGOS 战斗服务(匹配、房间)创建,也可以通过 PGOS 提供的 DS 托管独立 API 创建。

1.2 主要内容

DS Hosting 的集成路线

要将 PGOS DS Hosting 集成到您的游戏中,有两种路线可供选择:

  • 接入 PGOS 对战服务(Matchmaking 服务 / Lobby 服务 / World 服务),这些服务与 DS Hosting 服务无缝集成。例如,当匹配服务找到一些玩家进行对战时,会自动请求合适的 DS 资源。
  • 此外,游戏可以通过HTTP API直接访问DS Hosting服务,而无需使用PGOS对战服务。我们称之为DS Hosting服务的独立模式

以下是两种DS Hosting服务方式的对比:

Factors使用PGOS匹配和房间与DS Hosting服务使用独立的 AP单独集成DS Hosting服务
PGOS Client SDK必要非必要
PGOS Server SDK必要必要
DS Standalone API非必要必要
PGOS Player Auth必要非必要
Player IdentityPGOS 内置由游戏提供

PGOS Server SDK 的集成

无论选择哪种路线,您都需要在 游戏服务器(DS)中集成 PGOS Server SDK,以处理由 PGOS 对战服务或经由独立集成 HTTP API 发起的 DS 资源请求(在 PGOS 中我们称其为游戏会话放置请求)。

游戏服务器工作模式

考虑到不同游戏的需要,PGOS 支持两种游戏服务器工作模式:

  • 简单模式:每个游戏服务进程全生命期内可且仅可运行一个游戏会话。游戏服务进程应该尽快退出,当该游戏会话结束时。一般来说,此模式适合单游戏会话所需的硬件资源较多的场景。
  • 并行模式:每个游戏服务进程同一时间可以运行多个(存在上限)游戏会话,且不限制全生命期内运行的游戏会话数量。游戏服务进程将继续服务于其他游戏会话或等待新的游戏会话放置请求,当某个游戏会话结束时。一般来说,此模式适合单游戏会话所需硬件资源较少的场景,并行模式有助于最大程度的减少创建大量服务进程实例的边际开销。

游戏服务器工作模式所需的 PGOS Server SDK 接口存在差异,且必须且只能在 创建 fleet 时选择,并在 fleet 创建后不可修改。本文的主要内容均针对 简单模式并行模式 涉及的集成工作将在本文的“高级特性”章节介绍。

共享内存模式

某些游戏需要利用 Linux 平台的 fork 机制来创建 DS 实例,以提高闲置内存资源的利用率。目前,PGOS 已经支持了这种 DS 行为,我们将其命名为 DS 托管服务的共享内存模式。该功能涉及的集成工作将在本文的“高级特性”章节介绍。

2. 使用DS Hosting与匹配/房间/大厅服务

2.1 工作流程

sequenceDiagram participant C as Game Client participant B as PGOS Matchmaking/Lobby participant S as Game Server (DS) participant P as PGOS rect rgb(191, 222, 253) note right of S:Register game server P->>S:Launch game server process S->>P:API:ProcessReady() P-->>+S:Callback:OnHealthCheck() S->>-P:Health status end rect rgb(170, 190, 253) note right of C:Create/Acitvate battle session C->>B:StartMatchmaking/StartBattle B->>P:Create new battle session, request ds resource P->>P:Battle session:PENDING P-->>S:Callback:OnStartBattleSession() P->>P:Battle session:ACTIVATING S->>P:API:ActivateBattleSession() P->>P:Battle session:ACTIVATE P-->>B:Event:PlacementSuccess B->>C:Send game server connection info end rect rgb(191, 222, 253) note right of C:Player in and out C->>S:Connect to game server S->>P:API:AcceptPlayerBattleSession() C-->>S:Disconnect from game server and do not reconnect S->>P:API:RemovePlayerBattleSession() end rect rgb(191, 222, 253) note right of S:Shut down battle session S->>P:API:ProcessEnding() S->>S:Process shutdown note right of S:Deregister game server P->>P:Battle session:TERMINATED end
  • 注册游戏服务器 : 使用PGOS Server SDK将运行中的DS实例注册到DS Hosting服务,只有注册过的DS实例才会被分配战斗会话。
  • 创建/激活战斗会话 :PGOS战斗服务创建的战斗会话会被放置到已注册的DS上。在DS激活战斗会话后,PGOS会将DS访问信息推送给参与该战斗会话的游戏客户端。
  • 玩家进出:PGOS提供了一系列接口帮助DS验证和管理战斗中的玩家会话。
  • 关闭战斗会话 :当战斗会话结束后,DS需要通知PGOS并结束进程。PGOS会启动一个新的DS进程,为新的战斗会话做准备。

2.2 准备玩家延迟数据

在玩家成功登录后,PGOS会对所有可用的数据中心区域进行测速。测试得到的玩家延迟数据会以较低频率刷新,并在使用匹配、房间和大厅等服务时用于战斗会话的放置,目的是为玩家选择延迟更好的游戏服务器。 这个测速行为由PGOS Client SDK执行,通常不需要游戏进行额外处理。不过,我们仍然提供了一个接口供游戏客户端获取这些玩家延迟数据,示例代码如下:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto Battle = IPgosSDKCpp::Get().GetClientBattleAPI();
if (Battle)
{

Battle->GetDSHostingLatencies([]() {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("GetDSHostingLatencies Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("GetDSHostingLatencies Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

2.3 查询玩家当前的战斗会话

PGOS允许一名玩家同时加入多个战斗会话,游戏客户端可以通过DescribeBattleSessions接口查询玩家当前所在的战斗会话信息。该接口会返回玩家连接持有战斗会话的DS所需的所有信息,包括访问IP、端口和玩家战斗会话ID。

void SomeUObjectClass::Function()
{
FPgosResult Result = IPgosSDKCpp::Get().GetClientBattleAPI()->DescribeBattleSessions(
Player->PlayerBattleSessionID);

PGOS_CALL_ASYNC_API(UPgosClientAPIDescribeBattleSessions::DescribeBattleSessions,
this,
&SomeUObjectClass::DescribeBattleSessionsSuccess,
&SomeUObjectClass::DescribeBattleSessionsFailed);
}

void SomeUObjectClass::DescribeBattleSessionsSuccess(const FPgosResult& Error,const FClientDescribeBattleSessionsResult& result)
{
UE_LOG(LogTemp, Log, TEXT("DescribeBattleSessionsSuccess"))
}

void SomeUObjectClass::DescribeBattleSessionsFailed(const FPgosResult& Error,const FClientDescribeBattleSessionsResult& result)
{
UE_LOG(LogTemp, Log, TEXT("DescribeBattleSessionsFailed"))
}

2.4 通过PGOS战斗服务启动游戏会话

可以通过 PGOS 战斗服务创建战斗会话。阅读匹配集成了解如何通过匹配服务创建战斗会话。阅读房间集成了解如何通过房间服务创建战斗会话。

2.5 将玩家从游戏会话中移除

游戏客户端可以通过调用接口 TerminatePlayerBattleSession 将已登录的玩家从战斗会话中移除。调用完成后,该玩家将无法再进入此战斗会话,请谨慎使用。

void SomeUObjectClass::Function()
{
FPgosResult Result = IPgosSDKCpp::Get().GetClientBattleAPI()->DescribeBattleSessions(
Player->PlayerBattleSessionID);

PGOS_CALL_ASYNC_API(UPgosClientAPITerminatePlayerBattleSession::TerminatePlayerBattleSession,
this,
&SomeUObjectClass::DescribeBattleSessionsSuccess,
&SomeUObjectClass::DescribeBattleSessionsFailed,
BattleSessionId,
PlayerBattleSessionId);
}

void SomeUObjectClass::TerminatePlayerBattleSessionSuccess(const FPgosResult& Error,const FClientTerminatePlayerBattleSessionResult& result)
{
UE_LOG(LogTemp, Log, TEXT("TerminatePlayerBattleSessionSuccess"))
}

void SomeUObjectClass::TerminatePlayerBattleSessionFailed(const FPgosResult& Error,const FClientTerminatePlayerBattleSessionResult& result)
{
UE_LOG(LogTemp, Log, TEXT("TerminatePlayerBattleSessionFailed"))
}

2.6 游戏会话放置的事件

在整个战斗生命周期中,将触发以下事件:

  • event_battle_session_placement_fulfilled

  • event_battle_session_placement_timedout

  • event_battle_session_placement_failed

关于它们的数据结构,请参考 WebPortal \ Extensions \ Event。 关于 WebHook 封装,请参考 WebHook 文档

3. 独立集成DS 托管

3.1 工作流程

sequenceDiagram participant C as Game Client participant B as Game Backend Service participant S as Game Server (DS) participant P as PGOS rect rgb(191, 222, 253) note right of S:Register game server P->>S:Launch game server process S->>P:API:ProcessReady() P-->>+S:Callback:OnHealthCheck() S->>-P:Health status end rect rgb(170, 190, 253) note right of C:Create/Acitvate battle session C->>B:Request for a DS B->>P:API:StartBattleSessionPlacement() P->>P:Battle session:PENDING B->>P:API:DescribeBattleSessionPlacement() P-->>B:Battle session ID P-->>S:Callback:OnStartBattleSession() P->>P:Battle session:ACTIVATING S->>P:API:ActivateBattleSession() P->>P:Battle session:ACTIVATE P-->>B:Event:PlacementSuccess B->>C:Send game server connection info end rect rgb(191, 222, 253) note right of C:Player in and out C->>S:Connect to game server S->>P:API:AcceptPlayerBattleSession() C-->>S:Disconnect to game server and do not reconnect S->>P:API:RemovePlayerBattleSession() end rect rgb(191, 222, 253) note right of S:Shut down battle session S->>P:API:ProcessEnding() S->>S:Process shutdown note right of S:Deregister game server P->>P:Battle session:TERMINATED end
  • 注册游戏服务器:将正在运行的DS实例注册到DS 托管服务,只有注册的DS实例才会分配战斗会话。
  • 创建/激活战斗会话 :通过 独立 HTTP API创建的战斗会话放置在已经注册的DS上,当DS激活战斗会话后,PGOS 会将DS访问信息推送给参与战斗会话的游戏客户端。
  • 玩家加入和退出:PGOS 提供了一组接口帮助DS验证和管理战斗中的玩家会话。
  • 关闭战斗会话 :一旦战斗会话结束, DS需要通知 PGOS 并结束该进程,PGOS 会拉起一个新的DS进程,准备迎接新战斗会话的到来。

3.2 查看玩家当前的战斗会话

PGOS 允许玩家同时加入多个战斗会话,游戏可以通过 PGOS HTTP API QueryPlayerBattleSessions查询玩家当前正在参与的战斗会话信息。该接口会返回玩家连接战斗会话所在DS所需的所有信息,包括访问IP,端口,玩家战斗会话 ID。

3.3 启动战斗会话

可以从DS 托管 Standalone HTTP API 创建战斗会话。使用 RequestBattleSessionID接口在发起战斗会话 放置请求之前向 PGOS 后端请求战斗会话 ID。如果此接口发生任何错误,应中止战斗会话 放置工作。然后,使用 StartBattleSessionPlacement要求专用服务器并将战斗会话放在该服务器上。并使用 CancelBattleSessionPlacement接口终止正在进行的战斗会话 放置 。可以通过调用 [DescribeBattleSessionPlacement](../../../httpapi 来跟踪战斗会话 放置的进度#tag/Battle-Management/paths/~1dsm~1describe_battle_session_placement/post)接口或处理 PGOS 事件。可以使用 [AddPlayersToBattleSession](../../../httpapi 将玩家添加到战斗会话中#tag/Battle-Management/paths/~1dsm~1add_players_to_battle_session/post)界面。

3.4 将玩家从战斗会话中分离

游戏客户端可以通过调用接口TerminatePlayerBattleSession将已登录的玩家从战斗会话中分离出来。召唤完成后,玩家将无法再进入该战斗会话 ,请谨慎使用。

3.5 战斗会话放置的事件

在整个战斗生命周期中,将触发以下事件:

  • event_battle_session_placement_fulfilled
  • event_battle_session_placement_timedout
  • event_battle_session_placement_failed

关于它们的数据结构,请参考 WebPortal \ Extensions \ Event。 关于 WebHook 封装,请参考 WebHook 文档

4. 集成您的游戏服务器

本章节介绍如何在您的游戏服务器(DS)中集成 PGOS Server SDK,以使其可以成功部署到 PGOS Fleet 中。概要来说:

  • 每个游戏服务器进程都必须响应 PGOS 服务触发的事件以响应游戏会话的放置请求,
  • 同时还必须让 PGOS 了解服务器进程状态和玩家连接情况。

前置信息:

  • 在此了解如何将 PGOS SDK 设置到 Unreal Engine 项目中:初始设置
  • 在此了解如何将 PGOS SDK 设置到 Unity 项目中:初始设置

4.1 初始化服务器进程

这是一项必需的任务。 所有 API 和数据类型都组织在 PgosServerAPI.h 中。 添加代码以建立与 PGOS 服务的通信,并报告服务器进程已准备好托管战斗会话。此代码必须在任何依赖 PGOS 的代码(如玩家数据查询)之前运行。

#include "PgosSDKCpp.h"
#include "Core/PgosServerAPI.h"
#include "Core/PgosServerHostingAPI.h"

// Example codes in PGOS's sample 'Pingpong Shooter'
void APGOSBattleGameMode::PreparePgosSDK()
{
// fill config options of pgos
TMap<FString, FString> Config;
Config.Add(TEXT("title_id"), TEXT("xxx"));
Config.Add(TEXT("secret_key"), TEXT("yyy"));
Config.Add(TEXT("log_level"), TEXT("0"));
IPgosSDKCpp::Get().GetServerAPI()->InitConfig(Config);

// fill port
int32 Port = GetWorld()->NetDriver->LocalAddr->GetPort();

// fill log paths
TArray<FString> LogPathes;
FString ServerLogFilePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FGenericPlatformOutputDevices::GetAbsoluteLogFilename());
FString PgosSavedPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir());
FString PgosLogPath = FPaths::Combine(PgosSavedPath, FString("pgos"));
FString Binpath = FPaths::ConvertRelativePathToFull(
FPaths::Combine(FPaths::ProjectDir(), FString("Binaries")));

LogPathes.Add(PgosLogPath);
LogPathes.Add(ServerLogFilePath);

// bind callbacks
auto HostingAPI = IPgosSDKCpp::Get().GetServerHostingAPI();
HostingAPI->OnHealthCheck().AddUObject(this, &APGOSBattleGameMode::OnHealthCheck);
HostingAPI->OnStartBattleSession().AddUObject(this, &APGOSBattleGameMode::OnStartBattleSession);
HostingAPI->OnProcessTerminate().AddUObject(this, &APGOSBattleGameMode::OnProcessTerminate);
HostingAPI->OnTerminateBattleSession().AddUObject(this, &APGOSBattleGameMode::OnTerminateBattleSession);
HostingAPI->OnBattlePlayerOffline().AddUObject(this, &APGOSBattleGameMode::OnBattlePlayerOffline);
HostingAPI->OnBattlePlayerBanned().AddUObject(this, &APGOSBattleGameMode::OnBattlePlayerBanned);

// Notify PGOS that a server process is ready to host a battle session
FPgosResult Result = HostingAPI->ProcessReady(Port, LogPathes);
}
  1. 调用 InitConfig 初始化 PGOS DS SDK。

  2. 调用 ProcessReady 通知 PGOS 服务器进程已准备好主持战斗会话 ,并提供以下信息。

  • 端口号 由服务器进程使用。该端口号和 IP 地址将提供给游戏客户端,以便它们可以连接到服务器进程加入战斗会话 。
  • 您希望 PGOS 保留的日志文件的 log_paths。这些文件由服务器进程在战斗会话期间生成。它们临时存储在运行服务器进程的CVM实例上,并在CVM实例关闭时丢失。此处列出的任何路径都将在战斗会话结束后上传到 PGOS 后端。您可以在 PGOS 的门户上访问它们。点击这里了解更多详情。注意:不要将配置和其他资源文件放在 log_paths 中,否则 log_paths 中的文件可能会被清理。
  • PGOS 服务可以在服务器进程上调用的回调函数的名称。您的游戏服务器需要实现这些功能。本文档的其余部分将介绍实现这些功能的说明。
  • OnHealthCheck(必需)定期调用以从服务器进程请求健康状态报告。
  • 当 PGOS 服务收到在游戏服务器进程中启动新战斗会话的请求时,会调用 OnStartBattleSession(必需)。
  • 当 PGOS 服务需要强制终止服务器进程时,会调用 OnProcessTerminate(必需),以允许服务器进程正常关闭。
  • 当战斗会话中的玩家离线时,会调用 OnBattlePlayerOffline(可选)。
  • 当战斗会话中的玩家被禁止时,会调用 OnBattlePlayerBanned(可选)。

4.2 上报服务器进程健康状况

此任务为必需任务。

// Example codes in PGOS's sample 'Pingpong Shooter'
bool APGOSBattleGameMode::OnHealthCheck()
{
return true;
}
  • 添加代码以实现回调函数 onHealthCheck。PGOS 服务会调用此函数定期从服务器进程收集健康指标。服务器进程对健康检查的响应是一个布尔值:健康或不健康。 PGOS 使用服务器进程健康状态来高效地终止不健康的进程并释放资源。在以下情况下,PGOS 后端可能会关闭进程并启动一个新进程:

    • 游戏服务器进程连续三次健康检查报告不健康。- 游戏服务器进程连续三次健康检查没有响应。

4.3 激活战斗会话

这个任务是必需的。 添加代码以处理 OnStartBattleSession 通知。PGOS 服务调用此函数以在服务器进程上放置新的战斗会话。 OnStartBattleSession 接收一个 ServerBattleSession 对象作为输入参数。该对象包含关键的战斗会话信息,如战斗会话 ID、以 key-value 对形式的战斗属性、队伍结构和战斗成员信息。 要处理 OnStartBattleSession 通知,需要完成以下任务:

  • 基于 ServerBattleSession 对象进行新战斗会话的准备工作。
  • 当新的战斗会话准备好接受玩家时调用 ActivateBattleSession。对于成功的调用,PGOS 服务将把战斗会话状态更改为ACTIVE

4.4 验证新玩家

*此任务为可选

// Example codes in PGOS's sample 'Pingpong Shooter'
void APGOSBattleGameMode::PreLogin(
const FString& Options,
const FString& Address,
const FUniqueNetIdRepl& UniqueId,
FString& ErrorMessage)
{
...
#if UE_SERVER
FPgosResult Result = IPgosSDKCpp::Get().GetServerHostingAPI()->AcceptPlayerBattleSession(
CurrentBattleSession.battle_session_id,
PlayerBattleSessionID);
#else
...
}

添加代码以通过PGOS服务验证玩家连接请求。每当新玩家尝试连接到服务器进程时,在接受玩家连接之前都应运行此代码。玩家验证使PGOS能够跟踪战斗会话中的当前玩家和可用槽位。

来自游戏客户端的连接请求应包含玩家战斗会话ID。当游戏客户端请求加入战斗会话时(例如"开始匹配"请求),此ID由PGOS服务生成。该ID用于在战斗会话中预留玩家槽位。在收到游戏客户端连接请求时,服务器进程会使用玩家战斗会话ID调用AcceptPlayerBattleSession。然后PGOS会验证玩家战斗会话ID是否已在战斗会话中预留。一旦AcceptPlayerBattleSession返回True,玩家战斗会话的状态将变为Active。

一旦玩家战斗会话ID被PGOS验证通过,服务器进程就可以接受连接。如果玩家战斗会话ID未通过PGOS服务验证,服务器进程应拒绝连接。

如果DS选择跳过此任务,PGOS后端仍将正常维护Battle Session,并且不会根据player-battle-session状态的变化执行额外操作。

然而,我们仍然建议DS执行该操作,因为它不仅可以帮助DS验证指定玩家是否属于当前Battle Session,还允许PGOS后端更新player-battle-session状态,以便在PGOS控制台上正确显示(例如,Battle Session详情页面中 Connected 玩家数量,以及 Players In-out事件日志)。

4.5 报告玩家战斗会话断开连接

此任务为可选

// Example codes in PGOS's sample 'Pingpong Shooter'
void APGOSBattleGameMode::Logout(AController* Exiting)
{
#if UE_SERVER
FPgosResult Result = IPgosSDKCpp::Get().GetServerHostingAPI()->DisconnectPlayerBattleSession(
CurrentBattleSession.battle_session_id,
Player->PlayerBattleSessionID);
#endif
}

当玩家从游戏 DS 断开连接时,调用 DisconnectPlayerBattleSession 来通知 PGOS 服务。调用此接口后,玩家战斗会话的状态将变更为 Disconnected,在状态变为 Completed 之前,仍可以通过再次调用 AcceptPlayerBattleSession 将状态恢复为 Active

如果DS选择跳过此任务,PGOS后端仍将正常维护Battle Session,并且不会根据player-battle-session状态的变化执行额外操作。

但是,如果您通过API向PGOS报告相关事件,您将能够在PGOS控制台中查看更详细的Battle Session历史记录,这有助于分析某些游戏中异常现象的原因。

4.6 上报玩家战斗会话结束

此任务为可选

// Example codes in PGOS's sample 'Pingpong Shooter'
void APGOSBattleGameMode::Logout(AController* Exiting)
{
#if UE_SERVER
FPgosResult Result = IPgosSDKCpp::Get().GetServerHostingAPI()->RemovePlayerBattleSession(
CurrentBattleSession.battle_session_id,
Player->PlayerBattleSessionID);
#endif
}

当玩家离开Battle Session时,可调用RemovePlayerBattleSession通知PGOS后端。调用此API后,玩家战斗会话的状态将更改为Completed,PGOS将不接受这个player-battle-session进入这个Battle Session。

如果DS选择跳过此任务,PGOS后端仍将正常维护Battle Session,并且不会根据player-battle-session状态的变化执行额外操作。

但是,如果您通过API向PGOS报告相关事件,您将能够在PGOS控制台中查看更详细的Battle Session历史记录,这有助于分析某些游戏中异常现象的原因。

4.7 结束战斗会话

此任务是必需的 战斗会话结束后,游戏DS应该关闭,以回收和刷新托管资源。我们不支持在DS的一个生命周期内托管多个战斗会话。以下是游戏服务器进程退出流程的示例:

  1. 战斗会话结束。
  2. 为退出做自定义准备。
  3. 3.调用ProcessEnding
  4. 4.DS 进程自行终止。关于 DS接口的说明:-调用ProcessEnding会向 PGOS 发出进程即将结束的信号。如果进程在调用 ProcessEnding 后 2 分钟内没有退出,PGOS 将强制终止该进程。强制终止机制只是处理DS异常状态的***后备方案***,具有一定延迟,会***降低*** DS Fleet的战斗会话负载能力。因此,我们仍然建议在调用ProcessEnding`接口后DS主动终止。
  • 在本地模式下运行时, DS需要自行处理进程退出。

  • 在此DS进程上运行的战斗会话将立即终止。

4.8 响应服务器进程关闭通知

这是一个必需的任务。 当游戏服务器进程持续报告为不健康状态,或运行游戏服务器进程的实例即将终止时,将调用回调函数OnProcessTerminate

4.9 其他通知

游戏可以按需处理以下通知:

NotificationDescription
FPgosHostingAPI::OnBattleSessionUpdated当玩家被添加到战斗会话时触发。
FPgosHostingAPI::OnPlayerBattleSessionsTerminated当玩家终止其战斗会话时触发。
FPgosHostingAPI::OnBattlePlayerOffline当玩家的游戏客户端离线时触发。
FPgosHostingAPI::OnBattlePlayerBanned当玩家被 PGOS 封禁时触发。

4.10 测试集成

为了便于在非生产环境中快速部署游戏到DS上,PGOS提供了本地DS机制。这使您可以在本地(非云端)DS上部署游戏,而无需在DS或游戏客户端中添加任何额外代码。这种类型的DS作为可用的DS被包含在PGOS工作系统中,并分配给游戏客户端。这在将PGOS与您的游戏服务器集成时会很有用。点击这里了解更多详情。

5. 高级特性

5.1 共享内存模式

某些游戏需要利用 Linux 平台的 fork 机制来创建 DS 实例,以提高闲置内存资源的利用率。目前,PGOS 已经支持了这种 DS 行为,我们将其命名为 DS 托管服务的共享内存模式。 本节将说明 PGOS 如何支持可 fork 的 DS 的工作模式,以及如何让您的可 fork 的 DS 在 PGOS 托管服务中运行。

5.1.1 关键组成

image-20230516102158622

  • agent是运行CVM以实现DS Hosting服务的进程。该进程通过PGOS Server SDK为游戏服务器提供多个与托管服务相关的接口。对于游戏服务器进程来说,其存在是透明的。
  • 主游戏服务器是PGOS在共享内存模式下启动的唯一进程,它不会持有任何战斗会话。其作用是在适当的时候执行fork()操作创建子进程,并监控子进程数量和运行状态,以判断是否需要fork新的子进程。
  • 分叉游戏服务器是由主游戏服务器在共享内存模式下通过fork启动的进程。在共享内存模式下,战斗会话只会被分配到分叉游戏服务器上。

5.1.2 集成您的fork(父级)游戏服务器

为父进程创建PGOS

在调用fork函数的父进程中使用多线程可能会导致许多异常,因此PGOS Server SDK将父进程的调用与标准接口隔离开来。 父进程和子进程将使用相同的PGOS Server SDK文件,但会调用不同的接口来创建相应的PGOS实例。 在游戏项目的适当位置添加以下代码,为父进程创建PGOS:

void SomeClass::SomeFunc()
{
IPgosSDKCpp::Get().CreatePGOSForingProcess();
// ...
}

初始化Fork进程

通过调用 ServerHostingForking 模块的 ProcessReady 来通知 PGOS,一个工作在共享内存模式下的服务器进程即将fork子进程:

void SomeClass::SomeFunc()
{
if (auto ServerHostingFrokingAPI = IPgosSDKCpp::Get().GetServerHostingForkingAPI())
{
TArray<FString> LogPathes;
const auto Result = ServerHostingFrokingAPI->ProcessReady(LogPathes);
// ...
}
}

维持一定数量的子(Forked)进程

游戏需要在共享内存模式下通过fork游戏服务器来维护一些子游戏服务器,这在正常模式下由 PGOS CVM 托管服务负责。 提供了两个接口来帮助维护子进程的数量:

  • QueryDsInstancePerCVMConfig:查询 Fleet 的每个 CVM 配置的 DS 实例数,这是用户期望在当前 CVM 实例上运行的(子)DS 进程的最大有效数量。
  • QueryAvailableDsInstanceCount:查询当前 CVM 中在 PGOS 托管服务上注册的(子)DS 进程数量。此参数可用作父进程中fork操作的决策参考。

以上两个接口都在 ServerHostingForking 模块中提供:

void SomeClass::SomeFunc()
{
if (auto ServerHostingFrokingAPI = IPgosSDKCpp::Get().GetServerHostingForkingAPI())
{
int32 MaxDsCnt = HostingForkingAPI->QueryDsInstancePerCVMConfig();
int32 CurDsCnt = HostingForkingAPI->QueryAvailableDsInstanceCount();
// ...
}
}

5.1.3 集成您的子(forked)游戏服务器

子游戏服务器的集成过程与普通模式相同,请参阅章节 Integrate Your Game Server. 注意,在CVM实例生存期内,请勿更改游戏服务器的工作模式,因为这会导致CVM实例不可用。

5.1.4 示例代码

完成以上步骤后,您便可向您的游戏服务器添加fork逻辑,从而创建子游戏服务器,这些服务器将注册到 PGOS 托管服务,并可从 OnStartBattleSession 事件接收战斗会话放置请求。PGOS 示例:Ping-pong Shooter 现已支持共享内存模式,让您更轻松地体验该功能并了解相关 PGOS 接口的使用。

提示

注意 PGOS 示例中的fork实现仅供演示,请自行开发游戏中的fork逻辑,使其更符合游戏的实际需求。

选项需求

  • forking 为 Ping-pong Shooter 启用共享内存模式。

  • nothreading 启用引擎推荐的 UE 单线程模式。

  • fork工程实现UnixPlatformHelper.h/FUnixPlatformHelper::WaitAndFork() 该函数重写自 FUnixPlatformProcess::WaitAndFork(),简化子进程创建流程,并借助 ServerHostingForking 模块提供的接口监控子进程数量。

  • fork的时机:PGOS Sample 中涉及fork工程的是在 AGameModeBase::InitGame 函数中。

该时机可能不是您游戏的最佳选择,请根据游戏需求选择合适的fork时机。

下图展示了 PGOS Sample 中父、子游戏服务器进程的工作流程:

sequenceDiagram participant A as Forking Game Server participant B as Forked Game Server participant H as CVM Hosting rect rgb(170, 190, 253) note over A: ProcessReady for forking game server process A->>A:IPgosSDKCpp::CreatePGOSForingProcess() A->>H:FPgosHostingForkingAPI::ProcessReady() end A->>A:FUnixPlatformHelper::WaitAndFork() Loop Forking task work loop A->>A:check forked process amount alt Need to fork A->>B:fork(): forking a new game server process else Children are enough, try to wait A->>A:waitpid For forked process end end rect rgb(191, 222, 253) note over B: Prepare for forked game process B->>B:Restart GLog B->>B:Reset GameThreadId B->>B:Ohter preparations ... end rect rgb(170,190,253) note over A: ProcessReady for forked game server process B->>B:IPgosSDKCpp::CreatePGOS() B->>B:FPgosServerAPI::InitConfig() B->>H:FPgosHostingAPI::ProcessReady() end H-->>B:OnStartBattleSession() B->>B:Other game logic

5.2 游戏服务的并行模式

5.2.1 回顾简单模式

首先,让我们回顾 “简单模式” 下游戏服务器生命期与其持有的游戏会话生命期的关系,其如下图所示:

image-20250918115100512

游戏服务进程生命期:

  • 开始于:游戏进程被运行在 CVM 实例上的 agent 进程拉起。
  • 结束于:在完成 ProcessEnding 接口调用后,游戏进程应尽快主动退出。若未主动退出,其将被 agent 进程回收。

可放置游戏会话的窗口:

  • 开始于:调用 ProcessReady 接口后。
  • 结束于:接收到 OnStartBattleSession 事件后。

游戏会话的活动期:

  • 开始于:游戏调用 ActivateBattleSession 接口后。
  • 结束于:游戏调用 ProcessEnding 接口后。

简单模式下,一个游戏服务进程全生命期内仅服务一个游戏会话,即 OnStartBattleSession 事件最多只会触发一次。

5.2.2 并行模式

并行模式下游戏服务器生命期与其持有的游戏会话生命期的关系,其如下图所示:

image-20250918122311696

游戏服务进程生命期与简单模式相同,不再赘述。

可放置游戏会话的窗口则不同:

  • 开始于:调用 ProcessReady 接口后。
  • 结束于:调用 ProcessEnding 接口后。即直到服务进程准备退出(ProcessEnding)前,随时可能有游戏会话的放置请求到达(event OnStartBattleSession)。当然 PGOS 会控制并行的游戏会话不超过 fleet 中配置的数量。

游戏会话的活动期:

  • 开始于:游戏调用 ActivateBattleSession 接口后。
  • 结束于:
    • 通常来说,服务进程可以调用 TerminateBattleSession 接口终止特定的游戏会话,这是我们建议的常规操作。
    • 当 ProcessEnding 接口被调用后,该服务进程所持有的所有游戏会话会被强制终止,游戏需知晓该结果以防出现意料外的问题。