NetworkConnection.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using UnityEngine;
  5. namespace Mirror
  6. {
  7. /// <summary>Base NetworkConnection class for server-to-client and client-to-server connection.</summary>
  8. public abstract class NetworkConnection
  9. {
  10. public const int LocalConnectionId = 0;
  11. /// <summary>NetworkIdentities that this connection can see</summary>
  12. // DEPRECATED 2022-02-05
  13. [Obsolete("Cast to NetworkConnectionToClient to access .observing")]
  14. public HashSet<NetworkIdentity> observing => ((NetworkConnectionToClient)this).observing;
  15. /// <summary>Unique identifier for this connection that is assigned by the transport layer.</summary>
  16. // assigned by transport, this id is unique for every connection on server.
  17. // clients don't know their own id and they don't know other client's ids.
  18. public readonly int connectionId;
  19. /// <summary>Flag that indicates the client has been authenticated.</summary>
  20. public bool isAuthenticated;
  21. /// <summary>General purpose object to hold authentication data, character selection, tokens, etc.</summary>
  22. public object authenticationData;
  23. /// <summary>A server connection is ready after joining the game world.</summary>
  24. // TODO move this to ConnectionToClient so the flag only lives on server
  25. // connections? clients could use NetworkClient.ready to avoid redundant
  26. // state.
  27. public bool isReady;
  28. /// <summary>IP address of the connection. Can be useful for game master IP bans etc.</summary>
  29. public abstract string address { get; }
  30. /// <summary>Last time a message was received for this connection. Includes system and user messages.</summary>
  31. public float lastMessageTime;
  32. /// <summary>This connection's main object (usually the player object).</summary>
  33. public NetworkIdentity identity { get; internal set; }
  34. /// <summary>All NetworkIdentities owned by this connection. Can be main player, pets, etc.</summary>
  35. // IMPORTANT: this needs to be <NetworkIdentity>, not <uint netId>.
  36. // fixes a bug where DestroyOwnedObjects wouldn't find the
  37. // netId anymore: https://github.com/vis2k/Mirror/issues/1380
  38. // Works fine with NetworkIdentity pointers though.
  39. // DEPRECATED 2022-02-05
  40. [Obsolete("Cast to NetworkConnectionToClient to access .clientOwnedObjects")]
  41. public HashSet<NetworkIdentity> clientOwnedObjects => ((NetworkConnectionToClient)this).clientOwnedObjects;
  42. // batching from server to client & client to server.
  43. // fewer transport calls give us significantly better performance/scale.
  44. //
  45. // for a 64KB max message transport and 64 bytes/message on average, we
  46. // reduce transport calls by a factor of 1000.
  47. //
  48. // depending on the transport, this can give 10x performance.
  49. //
  50. // Dictionary<channelId, batch> because we have multiple channels.
  51. protected Dictionary<int, Batcher> batches = new Dictionary<int, Batcher>();
  52. /// <summary>last batch's remote timestamp. not interpolated. useful for NetworkTransform etc.</summary>
  53. // for any given NetworkMessage/Rpc/Cmd/OnSerialize, this was the time
  54. // on the REMOTE END when it was sent.
  55. //
  56. // NOTE: this is NOT in NetworkTime, it needs to be per-connection
  57. // because the server receives different batch timestamps from
  58. // different connections.
  59. public double remoteTimeStamp { get; internal set; }
  60. internal NetworkConnection()
  61. {
  62. // set lastTime to current time when creating connection to make
  63. // sure it isn't instantly kicked for inactivity
  64. lastMessageTime = Time.time;
  65. }
  66. internal NetworkConnection(int networkConnectionId) : this()
  67. {
  68. connectionId = networkConnectionId;
  69. }
  70. // TODO if we only have Reliable/Unreliable, then we could initialize
  71. // two batches and avoid this code
  72. protected Batcher GetBatchForChannelId(int channelId)
  73. {
  74. // get existing or create new writer for the channelId
  75. Batcher batch;
  76. if (!batches.TryGetValue(channelId, out batch))
  77. {
  78. // get max batch size for this channel
  79. int threshold = Transport.activeTransport.GetBatchThreshold(channelId);
  80. // create batcher
  81. batch = new Batcher(threshold);
  82. batches[channelId] = batch;
  83. }
  84. return batch;
  85. }
  86. // validate packet size before sending. show errors if too big/small.
  87. // => it's best to check this here, we can't assume that all transports
  88. // would check max size and show errors internally. best to do it
  89. // in one place in Mirror.
  90. // => it's important to log errors, so the user knows what went wrong.
  91. protected static bool ValidatePacketSize(ArraySegment<byte> segment, int channelId)
  92. {
  93. int max = Transport.activeTransport.GetMaxPacketSize(channelId);
  94. if (segment.Count > max)
  95. {
  96. Debug.LogError($"NetworkConnection.ValidatePacketSize: cannot send packet larger than {max} bytes, was {segment.Count} bytes");
  97. return false;
  98. }
  99. if (segment.Count == 0)
  100. {
  101. // zero length packets getting into the packet queues are bad.
  102. Debug.LogError("NetworkConnection.ValidatePacketSize: cannot send zero bytes");
  103. return false;
  104. }
  105. // good size
  106. return true;
  107. }
  108. // Send stage one: NetworkMessage<T>
  109. /// <summary>Send a NetworkMessage to this connection over the given channel.</summary>
  110. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  111. public void Send<T>(T message, int channelId = Channels.Reliable)
  112. where T : struct, NetworkMessage
  113. {
  114. using (NetworkWriterPooled writer = NetworkWriterPool.Get())
  115. {
  116. // pack message and send allocation free
  117. MessagePacking.Pack(message, writer);
  118. NetworkDiagnostics.OnSend(message, channelId, writer.Position, 1);
  119. Send(writer.ToArraySegment(), channelId);
  120. }
  121. }
  122. // Send stage two: serialized NetworkMessage as ArraySegment<byte>
  123. // internal because no one except Mirror should send bytes directly to
  124. // the client. they would be detected as a message. send messages instead.
  125. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  126. internal virtual void Send(ArraySegment<byte> segment, int channelId = Channels.Reliable)
  127. {
  128. //Debug.Log($"ConnectionSend {this} bytes:{BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}");
  129. // add to batch no matter what.
  130. // batching will try to fit as many as possible into MTU.
  131. // but we still allow > MTU, e.g. kcp max packet size 144kb.
  132. // those are simply sent as single batches.
  133. //
  134. // IMPORTANT: do NOT send > batch sized messages directly:
  135. // - data race: large messages would be sent directly. small
  136. // messages would be sent in the batch at the end of frame
  137. // - timestamps: if batching assumes a timestamp, then large
  138. // messages need that too.
  139. //
  140. // NOTE: we ALWAYS batch. it's not optional, because the
  141. // receiver needs timestamps for NT etc.
  142. //
  143. // NOTE: we do NOT ValidatePacketSize here yet. the final packet
  144. // will be the full batch, including timestamp.
  145. GetBatchForChannelId(channelId).AddMessage(segment, NetworkTime.localTime);
  146. }
  147. // Send stage three: hand off to transport
  148. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  149. protected abstract void SendToTransport(ArraySegment<byte> segment, int channelId = Channels.Reliable);
  150. // flush batched messages at the end of every Update.
  151. internal virtual void Update()
  152. {
  153. // go through batches for all channels
  154. foreach (KeyValuePair<int, Batcher> kvp in batches)
  155. {
  156. // make and send as many batches as necessary from the stored
  157. // messages.
  158. Batcher batcher = kvp.Value;
  159. using (NetworkWriterPooled writer = NetworkWriterPool.Get())
  160. {
  161. // make a batch with our local time (double precision)
  162. while (batcher.GetBatch(writer))
  163. {
  164. // validate packet before handing the batch to the
  165. // transport. this guarantees that we always stay
  166. // within transport's max message size limit.
  167. // => just in case transport forgets to check it
  168. // => just in case mirror miscalulated it etc.
  169. ArraySegment<byte> segment = writer.ToArraySegment();
  170. if (ValidatePacketSize(segment, kvp.Key))
  171. {
  172. // send to transport
  173. SendToTransport(segment, kvp.Key);
  174. //UnityEngine.Debug.Log($"sending batch of {writer.Position} bytes for channel={kvp.Key} connId={connectionId}");
  175. // reset writer for each new batch
  176. writer.Position = 0;
  177. }
  178. }
  179. }
  180. }
  181. }
  182. /// <summary>Check if we received a message within the last 'timeout' seconds.</summary>
  183. internal virtual bool IsAlive(float timeout) => Time.time - lastMessageTime < timeout;
  184. /// <summary>Disconnects this connection.</summary>
  185. // for future reference, here is how Disconnects work in Mirror.
  186. //
  187. // first, there are two types of disconnects:
  188. // * voluntary: the other end simply disconnected
  189. // * involuntary: server disconnects a client by itself
  190. //
  191. // UNET had special (complex) code to handle both cases differently.
  192. //
  193. // Mirror handles both cases the same way:
  194. // * Disconnect is called from TOP to BOTTOM
  195. // NetworkServer/Client -> NetworkConnection -> Transport.Disconnect()
  196. // * Disconnect is handled from BOTTOM to TOP
  197. // Transport.OnDisconnected -> ...
  198. //
  199. // in other words, calling Disconnect() does no cleanup whatsoever.
  200. // it simply asks the transport to disconnect.
  201. // then later the transport events will do the clean up.
  202. public abstract void Disconnect();
  203. public override string ToString() => $"connection({connectionId})";
  204. }
  205. }