feature: NetworkConnectionToClient.rtt via Ping & Pong Messages (#3545)

* NetworkConnectionToClient: send Ping message every PingFrequency

* breaking: NetworkPing/PongMessage .clientTime renamed to .localTime because it'll be used in both directions

* Server->Client->Server Ping/Pong messages and rtt

* don't ping in host mode

* adjust tests

* TODO
This commit is contained in:
mischa 2023-07-18 17:19:12 +08:00 committed by GitHub
parent 398d2e6d2c
commit 483006eadc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 11 deletions

View File

@ -29,6 +29,9 @@ internal override void Send(ArraySegment<byte> segment, int channelId = Channels
// true because local connections never timeout // true because local connections never timeout
internal override bool IsAlive(float timeout) => true; internal override bool IsAlive(float timeout) => true;
// don't ping host client in host mode
protected override void UpdatePing() {}
internal void DisconnectInternal() internal void DisconnectInternal()
{ {
// set not ready and handle clientscene disconnect in any case // set not ready and handle clientscene disconnect in any case

View File

@ -111,22 +111,21 @@ public struct EntityStateMessage : NetworkMessage
public ArraySegment<byte> payload; public ArraySegment<byte> payload;
} }
// A client sends this message to the server // whoever wants to measure rtt, sends this to the other end.
// to calculate RTT and synchronize time
public struct NetworkPingMessage : NetworkMessage public struct NetworkPingMessage : NetworkMessage
{ {
public double clientTime; public double localTime;
public NetworkPingMessage(double value) public NetworkPingMessage(double value)
{ {
clientTime = value; localTime = value;
} }
} }
// The server responds with this message // the other end responds with this message.
// The client can use this to calculate RTT and sync time // we can use this to calculate rtt.
public struct NetworkPongMessage : NetworkMessage public struct NetworkPongMessage : NetworkMessage
{ {
public double clientTime; public double localTime;
} }
} }

View File

@ -471,6 +471,7 @@ internal static void RegisterMessageHandlers(bool hostMode)
RegisterHandler<ObjectDestroyMessage>(OnObjectDestroy); RegisterHandler<ObjectDestroyMessage>(OnObjectDestroy);
RegisterHandler<ObjectHideMessage>(OnObjectHide); RegisterHandler<ObjectHideMessage>(OnObjectHide);
RegisterHandler<NetworkPongMessage>(NetworkTime.OnClientPong, false); RegisterHandler<NetworkPongMessage>(NetworkTime.OnClientPong, false);
RegisterHandler<NetworkPingMessage>(NetworkTime.OnClientPing, false);
RegisterHandler<SpawnMessage>(OnSpawn); RegisterHandler<SpawnMessage>(OnSpawn);
RegisterHandler<ObjectSpawnStartedMessage>(OnObjectSpawnStarted); RegisterHandler<ObjectSpawnStartedMessage>(OnObjectSpawnStarted);
RegisterHandler<ObjectSpawnFinishedMessage>(OnObjectSpawnFinished); RegisterHandler<ObjectSpawnFinishedMessage>(OnObjectSpawnFinished);

View File

@ -46,6 +46,14 @@ public class NetworkConnectionToClient : NetworkConnection
// Snapshot Buffer size limit to avoid ever growing list memory consumption attacks from clients. // Snapshot Buffer size limit to avoid ever growing list memory consumption attacks from clients.
public int snapshotBufferSizeLimit = 64; public int snapshotBufferSizeLimit = 64;
// ping for rtt (round trip time)
// useful for statistics, lag compensation, etc.
double lastPingTime = 0;
internal ExponentialMovingAverage _rtt = new ExponentialMovingAverage(NetworkTime.PingWindowSize);
/// <summary>Round trip time (in seconds) that it takes a message to go server->client->server.</summary>
public double rtt => _rtt.Value;
public NetworkConnectionToClient(int networkConnectionId) public NetworkConnectionToClient(int networkConnectionId)
: base(networkConnectionId) : base(networkConnectionId)
{ {
@ -175,12 +183,29 @@ internal void BufferRpc(RpcMessage message, int channelId)
} }
} }
protected virtual void UpdatePing()
{
// localTime (double) instead of Time.time for accuracy over days
if (NetworkTime.localTime >= lastPingTime + NetworkTime.PingInterval)
{
// TODO it would be safer for the server to store the last N
// messages' timestamp and only send a message number.
// This way client's can't just modify the timestamp.
NetworkPingMessage pingMessage = new NetworkPingMessage(NetworkTime.localTime);
Send(pingMessage, Channels.Unreliable);
lastPingTime = NetworkTime.localTime;
}
}
internal override void Update() internal override void Update()
{ {
// send rpc buffers // send rpc buffers
FlushRpcs(reliableRpcs, Channels.Reliable); FlushRpcs(reliableRpcs, Channels.Reliable);
FlushRpcs(unreliableRpcs, Channels.Unreliable); FlushRpcs(unreliableRpcs, Channels.Unreliable);
// ping client for rtt
UpdatePing();
// call base update to flush out batched messages // call base update to flush out batched messages
base.Update(); base.Update();
} }

View File

@ -266,6 +266,7 @@ internal static void RegisterMessageHandlers()
RegisterHandler<ReadyMessage>(OnClientReadyMessage); RegisterHandler<ReadyMessage>(OnClientReadyMessage);
RegisterHandler<CommandMessage>(OnCommandMessage); RegisterHandler<CommandMessage>(OnCommandMessage);
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false); RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false);
RegisterHandler<NetworkPongMessage>(NetworkTime.OnServerPong, false);
RegisterHandler<EntityStateMessage>(OnEntityStateMessage, true); RegisterHandler<EntityStateMessage>(OnEntityStateMessage, true);
RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage, true); RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage, true);
} }

View File

@ -109,15 +109,16 @@ internal static void UpdateClient()
} }
} }
// client rtt calculation //////////////////////////////////////////////
// executed at the server when we receive a ping message // executed at the server when we receive a ping message
// reply with a pong containing the time from the client // reply with a pong containing the time from the client
// and time from the server // and time from the server
internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMessage message) internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMessage message)
{ {
// Debug.Log($"OnPingServerMessage conn:{conn}"); // Debug.Log($"OnServerPing conn:{conn}");
NetworkPongMessage pongMessage = new NetworkPongMessage NetworkPongMessage pongMessage = new NetworkPongMessage
{ {
clientTime = message.clientTime, localTime = message.localTime,
}; };
conn.Send(pongMessage, Channels.Unreliable); conn.Send(pongMessage, Channels.Unreliable);
} }
@ -128,8 +129,32 @@ internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMes
internal static void OnClientPong(NetworkPongMessage message) internal static void OnClientPong(NetworkPongMessage message)
{ {
// how long did this message take to come back // how long did this message take to come back
double newRtt = localTime - message.clientTime; double newRtt = localTime - message.localTime;
_rtt.Add(newRtt); _rtt.Add(newRtt);
} }
// server rtt calculation //////////////////////////////////////////////
// Executed at the client when we receive a ping message from the server.
// in other words, this is for server sided ping + rtt calculation.
// reply with a pong containing the time from the server
internal static void OnClientPing(NetworkPingMessage message)
{
// Debug.Log($"OnClientPing conn:{conn}");
NetworkPongMessage pongMessage = new NetworkPongMessage
{
localTime = message.localTime,
};
NetworkClient.Send(pongMessage, Channels.Unreliable);
}
// Executed at the server when we receive a Pong message back.
// find out how long it took since we sent the Ping
// and update time offset
internal static void OnServerPong(NetworkConnectionToClient conn, NetworkPongMessage message)
{
// how long did this message take to come back
double newRtt = localTime - message.localTime;
conn._rtt.Add(newRtt);
}
} }
} }

View File

@ -35,6 +35,7 @@ public void Send_BatchesUntilUpdate()
{ {
// create connection and send // create connection and send
NetworkConnectionToClient connection = new NetworkConnectionToClient(42); NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
NetworkTime.PingInterval = float.MaxValue; // disable ping for this test
byte[] message = {0x01, 0x02}; byte[] message = {0x01, 0x02};
connection.Send(new ArraySegment<byte>(message)); connection.Send(new ArraySegment<byte>(message));
@ -63,6 +64,7 @@ public void SendBatchingResetsPreviousWriter()
// create connection // create connection
NetworkConnectionToClient connection = new NetworkConnectionToClient(42); NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
NetworkTime.PingInterval = float.MaxValue; // disable ping for this test
// send and update big message // send and update big message
byte[] message = {0x01, 0x02}; byte[] message = {0x01, 0x02};