UE实现获取客户端与服务器的延迟
思路简述#
主要是采用NTP的思想:
图中的时间点参数:
- Client's Send Time (t1): 客户端发送请求时的时间戳
- Server's Receive Time (t2): 服务器收到请求的时间戳
- Server's Transmit Time (t3): 服务器发送回应的时间戳
- Client's Receive Time (t4): 客户端收到服务器回应的时间戳
因为在UE中t2与t3太过于接近,于是就将两个时间合并仅有t3。
实现#
以下是参考代码,采用UE自带的RPC实现。
AMyPlayerController.h:
class AMyPlayerController : public APlayerController
{
GENERATED_BODY()
// ... other things
// TIME SYNC
public:
void StartSynchronization();
private:
// RPC
UFUNCTION(Server, Reliable)
void Server_HandleTimeSyncRequest(float ClientTimestamp);
UFUNCTION(Client, Reliable)
void Client_ReceiveServerTime(float ClientTimestamp, float ServerTimestamp);
void CalculateTimeOffset(float ClientTimestamp, float ServerTimestamp);
// 为了求NumberOfSyncs的平均时延
TArray<float> TimeOffsets;
int32 NumberOfSyncs;
int32 CurrentSyncIndex;
void SendNextSyncRequest();
void CalculateAverageOffset();
};
AMyPlayerController.cpp:
AMyPlayerController::AMyPlayerController()
{
// ....
// other init
NumberOfSyncs = 10; // Number of sync requests
CurrentSyncIndex = 0;
}
void AMyPlayerController::BeginPlay()
{
// Call the base class
Super::BeginPlay();
// ...
// other code
// only client calls
if (!HasAuthority())
{
StartSynchronization();
}
}
void AMyPlayerController::StartSynchronization()
{
TimeOffsets.Empty();
SendNextSyncRequest();
}
void AMyPlayerController::SendNextSyncRequest()
{
if (CurrentSyncIndex < NumberOfSyncs)
{
if (HasAuthority())
{
// Server does nothing here
}
else
{
float ClientTimestamp = GetWorld()->GetTimeSeconds();
Server_HandleTimeSyncRequest(ClientTimestamp);
}
}
else
{
CalculateAverageOffset();
}
}
void AMyPlayerController::Server_HandleTimeSyncRequest_Implementation(float ClientTimestamp)
{
float ServerTimestamp = GetWorld()->GetTimeSeconds();
Client_ReceiveServerTime(ClientTimestamp, ServerTimestamp);
}
void AMyPlayerController::Client_ReceiveServerTime_Implementation(float ClientTimestamp, float ServerTimestamp)
{
CalculateTimeOffset(ClientTimestamp, ServerTimestamp);
CurrentSyncIndex++;
SendNextSyncRequest(); // Send next request
}
void AMyPlayerController::CalculateTimeOffset(float ClientTimestamp, float ServerTimestamp)
{
float CurrentClientTime = GetWorld()->GetTimeSeconds();
float RoundTripTime = CurrentClientTime - ClientTimestamp;
float Offset = ServerTimestamp - ClientTimestamp - RoundTripTime / 2.0f;
TimeOffsets.Add(Offset);
AverTimeOffset = Offset;
}
void AMyPlayerController::CalculateAverageOffset()
{
float SumOffsets = 0.0f;
for (float Offset : TimeOffsets)
{
SumOffsets += Offset;
}
AverTimeOffset = SumOffsets / TimeOffsets.Num();
}
基本上的调用流程就是:
-
Client调用
SendNextSyncRequest()
, -
这个函数中调用了Server的RPC即
Server_HandleTimeSyncRequest_Implementation(t0)
,把客户端自己的时间传过去 -
Server的这个RPC中又调用了Client的RPC即
Client_ReceiveServerTime_Implementation(t0, t1)
, -
客户端在调用
Client_ReceiveServerTime_Implementation(t0, t1)
时会调用t3 = GetWorld()->GetTimeSeconds()
, -
t0, t1, t3三个时间点凑齐,开始计算时延
最后还暴露了一个参数NumberOfSyncs
,用于调整获取NumberOfSyncs
次时延的平均值。
疑问#
Q1: 为什么放在APlayerController中?#
因为RPC的调用有要求,需要客户端实际拥有这个Actor,显然APlayerController是符合这个要求。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
2020-01-11 【Codechef】A Triangle and Two Squares-Problem Code: SQRTRI