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
internal override bool IsAlive(float timeout) => true;
// don't ping host client in host mode
protected override void UpdatePing() {}
internal void DisconnectInternal()
{
// set not ready and handle clientscene disconnect in any case

View File

@ -111,22 +111,21 @@ public struct EntityStateMessage : NetworkMessage
public ArraySegment<byte> payload;
}
// A client sends this message to the server
// to calculate RTT and synchronize time
// whoever wants to measure rtt, sends this to the other end.
public struct NetworkPingMessage : NetworkMessage
{
public double clientTime;
public double localTime;
public NetworkPingMessage(double value)
{
clientTime = value;
localTime = value;
}
}
// The server responds with this message
// The client can use this to calculate RTT and sync time
// the other end responds with this message.
// we can use this to calculate rtt.
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<ObjectHideMessage>(OnObjectHide);
RegisterHandler<NetworkPongMessage>(NetworkTime.OnClientPong, false);
RegisterHandler<NetworkPingMessage>(NetworkTime.OnClientPing, false);
RegisterHandler<SpawnMessage>(OnSpawn);
RegisterHandler<ObjectSpawnStartedMessage>(OnObjectSpawnStarted);
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.
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)
: 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()
{
// send rpc buffers
FlushRpcs(reliableRpcs, Channels.Reliable);
FlushRpcs(unreliableRpcs, Channels.Unreliable);
// ping client for rtt
UpdatePing();
// call base update to flush out batched messages
base.Update();
}
@ -250,7 +275,7 @@ internal void DestroyOwnedObjects()
{
// unspawn scene objects, destroy instantiated objects.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3538
if (netIdentity.sceneId != 0)
if (netIdentity.sceneId != 0)
NetworkServer.UnSpawn(netIdentity.gameObject);
else
NetworkServer.Destroy(netIdentity.gameObject);

View File

@ -266,6 +266,7 @@ internal static void RegisterMessageHandlers()
RegisterHandler<ReadyMessage>(OnClientReadyMessage);
RegisterHandler<CommandMessage>(OnCommandMessage);
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false);
RegisterHandler<NetworkPongMessage>(NetworkTime.OnServerPong, false);
RegisterHandler<EntityStateMessage>(OnEntityStateMessage, 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
// reply with a pong containing the time from the client
// and time from the server
internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMessage message)
{
// Debug.Log($"OnPingServerMessage conn:{conn}");
// Debug.Log($"OnServerPing conn:{conn}");
NetworkPongMessage pongMessage = new NetworkPongMessage
{
clientTime = message.clientTime,
localTime = message.localTime,
};
conn.Send(pongMessage, Channels.Unreliable);
}
@ -128,8 +129,32 @@ internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMes
internal static void OnClientPong(NetworkPongMessage message)
{
// how long did this message take to come back
double newRtt = localTime - message.clientTime;
double newRtt = localTime - message.localTime;
_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
NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
NetworkTime.PingInterval = float.MaxValue; // disable ping for this test
byte[] message = {0x01, 0x02};
connection.Send(new ArraySegment<byte>(message));
@ -63,6 +64,7 @@ public void SendBatchingResetsPreviousWriter()
// create connection
NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
NetworkTime.PingInterval = float.MaxValue; // disable ping for this test
// send and update big message
byte[] message = {0x01, 0x02};