集成
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托管的独立模式
PGOS对战服务(匹配和房间)与DS托管服务无缝集成。例如,当匹配服务找到一些玩家进行对战时,会自动请求合适的DS资源。 此外,游戏可以通过HTTP API直接访问DS托管服务,而无需使用PGOS对战服务。我们称之为DS托管服务的独立模式。以下是两种DS托管服务方式的对比:
Factors | 使用PGOS匹配和房间与DS托管服务 | 使用独立的 AP单独集成DS托管服务 |
---|---|---|
PGOS Client SDK | 必要 | 非必要 |
PGOS Server SDK | 必要 | 必要 |
DS Standalone API | 非必要 | 必要 |
PGOS Player Auth | 必要 | 非必要 |
Player Identity | PGOS 内置 | 由游戏提供 |
2. 使用DS托管与匹配/房间/大厅服务
2.1 工作流程
- 注册游戏服务器 : 使用PGOS Server SDK将运行中的DS实例注册到DS托管服务,只有注册过的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 战斗服务创建战斗会话。阅读匹配集成了解如何通过匹配服务创建战斗会话。阅读房间集成了解如何通过房间服务创建战斗会话。
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 工作流程
- 注册游戏服务器:将正在运行的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. 集成您的游戏服务器
部署游戏服务器后,需要与 PGOS 服务进行通信。每个游戏服务器进程都必须响应 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);
}
调用
InitConfig
初始化 PGOS DS SDK。调用
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服务验证,服务器进程应拒绝连接。
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。
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
}
4.7 结束战斗会话
此任务是必需的。 战斗会话结束后,游戏DS应该关闭,以回收和刷新托管资源。我们不支持在DS的一个生命周期内托管多个战斗会话。以下是游戏服务器进程退出流程的示例:
- 战斗会话结束。
- 为退出做自定义准备。
- 3.调用
ProcessEnding
。 - 4.DS 进程自行终止。关于
DS接口的说明:-调用
ProcessEnding会向 PGOS 发出进程即将结束的信号。如果进程在调用 ProcessEnding 后 2 分钟内没有退出,PGOS 将强制终止该进程。强制终止机制只是处理DS异常状态的***后备方案***,具有一定延迟,会***降低*** DS Fleet的战斗会话负载能力。因此,我们仍然建议在调用
ProcessEnding`接口后DS主动终止。
在本地模式下运行时, DS需要自行处理进程退出。
在此DS进程上运行的战斗会话将立即终止。
接口“TerminateBattleSession”在版本 v 0.9.0 中已弃用。
4.8 响应服务器进程关闭通知
这是一个必需的任务。 当游戏服务器进程持续报告为不健康状态,或运行游戏服务器进程的实例即将终止时,将调用回调函数
OnProcessTerminate
。4.9 其他通知
游戏可以根据需要处理以下通知:
4.9 Other Notifications
游戏可以按需处理以下通知:
Notification | Description |
---|---|
FPgosHostingAPI::OnBattleSessionUpdated | 当玩家被添加到战斗会话时触发。 |
FPgosHostingAPI::OnPlayerBattleSessionsTerminated | 当玩家终止其战斗会话时触发。 |
FPgosHostingAPI::OnBattlePlayerOffline | 当玩家的游戏客户端离线时触发。 |
FPgosHostingAPI::OnBattlePlayerBanned | 当玩家被 PGOS 封禁时触发。 |
4.10 测试集成
为了便于在非生产环境中快速部署游戏到DS上,PGOS提供了本地DS机制。这使您可以在本地(非云端)DS上部署游戏,而无需在DS或游戏客户端中添加任何额外代码。这种类型的DS作为可用的DS被包含在PGOS工作系统中,并分配给游戏客户端。这在将PGOS与您的游戏服务器集成时会很有用。点击这里了解更多详情。
5. 共享内存模式
某些游戏需要利用 Linux 平台的 fork 机制来创建 DS 实例,以提高闲置内存资源的利用率。目前,PGOS 已经支持了这种 DS 行为,我们将其命名为 DS 托管服务的共享内存模式。 本节将说明 PGOS 如何支持可 fork 的 DS 的工作模式,以及如何让您的可 fork 的 DS 在 PGOS 托管服务中运行。
5.1 关键组成
- agent是运行CVM以实现DS托管服务的进程。该进程通过PGOS Server SDK为游戏服务器提供多个与托管服务相关的接口。对于游戏服务器进程来说,其存在是透明的。
- 主游戏服务器是PGOS在共享内存模式下启动的唯一进程,它不会持有任何战斗会话。其作用是在适当的时候执行fork()操作创建子进程,并监控子进程数量和运行状态,以判断是否需要fork新的子进程。
- 分叉游戏服务器是由主游戏服务器在共享内存模式下通过fork启动的进程。在共享内存模式下,战斗会话只会被分配到分叉游戏服务器上。
5.2 集成您的fork(父级)游戏服务器
5.2.1 为父进程创建PGOS
在调用fork函数的父进程中使用多线程可能会导致许多异常,因此PGOS Server SDK将父进程的调用与标准接口隔离开来。 父进程和子进程将使用相同的PGOS Server SDK文件,但会调用不同的接口来创建相应的PGOS实例。 在游戏项目的适当位置添加以下代码,为父进程创建PGOS:
void SomeClass::SomeFunc()
{
IPgosSDKCpp::Get().CreatePGOSForingProcess();
// ...
}
5.2.2 初始化Fork进程
通过调用 ServerHostingForking
模块的 ProcessReady
来通知 PGOS,一个工作在共享内存模式下的服务器进程即将fork子进程:
void SomeClass::SomeFunc()
{
if (auto ServerHostingFrokingAPI = IPgosSDKCpp::Get().GetServerHostingForkingAPI())
{
TArray<FString> LogPathes;
const auto Result = ServerHostingFrokingAPI->ProcessReady(LogPathes);
// ...
}
}
5.2.3 维持一定数量的子(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.3 集成您的子(forked)游戏服务器
子游戏服务器的集成过程与普通模式相同,请参阅章节 Integrate Your Game Server. 注意,在CVM实例生存期内,请勿更改游戏服务器的工作模式,因为这会导致CVM实例不可用。
5.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 中父、子游戏服务器进程的工作流程: