TelepathyTransport.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // wraps Telepathy for use as HLAPI TransportLayer
  2. using System;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using UnityEngine;
  6. // Replaced by Kcp November 2020
  7. namespace Mirror
  8. {
  9. [HelpURL("https://github.com/vis2k/Telepathy/blob/master/README.md")]
  10. [DisallowMultipleComponent]
  11. public class TelepathyTransport : Transport, PortTransport
  12. {
  13. // scheme used by this transport
  14. // "tcp4" means tcp with 4 bytes header, network byte order
  15. public const string Scheme = "tcp4";
  16. public ushort port = 7777;
  17. public ushort Port { get => port; set => port=value; }
  18. [Header("Common")]
  19. [Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")]
  20. public bool NoDelay = true;
  21. [Tooltip("Send timeout in milliseconds.")]
  22. public int SendTimeout = 5000;
  23. [Tooltip("Receive timeout in milliseconds. High by default so users don't time out during scene changes.")]
  24. public int ReceiveTimeout = 30000;
  25. [Header("Server")]
  26. [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")]
  27. public int serverMaxMessageSize = 16 * 1024;
  28. [Tooltip("Server processes a limit amount of messages per tick to avoid a deadlock where it might end up processing forever if messages come in faster than we can process them.")]
  29. public int serverMaxReceivesPerTick = 10000;
  30. [Tooltip("Server send queue limit per connection for pending messages. Telepathy will disconnect a connection's queues reach that limit for load balancing. Better to kick one slow client than slowing down the whole server.")]
  31. public int serverSendQueueLimitPerConnection = 10000;
  32. [Tooltip("Server receive queue limit per connection for pending messages. Telepathy will disconnect a connection's queues reach that limit for load balancing. Better to kick one slow client than slowing down the whole server.")]
  33. public int serverReceiveQueueLimitPerConnection = 10000;
  34. [Header("Client")]
  35. [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker host might send multiple fake packets with 2GB headers, causing the connected clients to run out of memory after allocating multiple large packets.")]
  36. public int clientMaxMessageSize = 16 * 1024;
  37. [Tooltip("Client processes a limit amount of messages per tick to avoid a deadlock where it might end up processing forever if messages come in faster than we can process them.")]
  38. public int clientMaxReceivesPerTick = 1000;
  39. [Tooltip("Client send queue limit for pending messages. Telepathy will disconnect if the connection's queues reach that limit in order to avoid ever growing latencies.")]
  40. public int clientSendQueueLimit = 10000;
  41. [Tooltip("Client receive queue limit for pending messages. Telepathy will disconnect if the connection's queues reach that limit in order to avoid ever growing latencies.")]
  42. public int clientReceiveQueueLimit = 10000;
  43. Telepathy.Client client;
  44. Telepathy.Server server;
  45. // scene change message needs to halt message processing immediately
  46. // Telepathy.Tick() has a enabledCheck parameter that we can use, but
  47. // let's only allocate it once.
  48. Func<bool> enabledCheck;
  49. void Awake()
  50. {
  51. // tell Telepathy to use Unity's Debug.Log
  52. Telepathy.Log.Info = Debug.Log;
  53. Telepathy.Log.Warning = Debug.LogWarning;
  54. Telepathy.Log.Error = Debug.LogError;
  55. // allocate enabled check only once
  56. enabledCheck = () => enabled;
  57. Debug.Log("TelepathyTransport initialized!");
  58. }
  59. // C#'s built in TCP sockets run everywhere except on WebGL
  60. // Do not change this back to using Application.platform
  61. // because that doesn't work in the Editor!
  62. public override bool Available() =>
  63. #if UNITY_WEBGL
  64. false;
  65. #else
  66. true;
  67. #endif
  68. // client
  69. private void CreateClient()
  70. {
  71. // create client
  72. client = new Telepathy.Client(clientMaxMessageSize);
  73. // client hooks
  74. // other systems hook into transport events in OnCreate or
  75. // OnStartRunning in no particular order. the only way to avoid
  76. // race conditions where telepathy uses OnConnected before another
  77. // system's hook (e.g. statistics OnData) was added is to wrap
  78. // them all in a lambda and always call the latest hook.
  79. // (= lazy call)
  80. client.OnConnected = () => OnClientConnected.Invoke();
  81. client.OnData = (segment) => OnClientDataReceived.Invoke(segment, Channels.Reliable);
  82. // fix: https://github.com/vis2k/Mirror/issues/3287
  83. // Telepathy may call OnDisconnected twice.
  84. // Mirror may have cleared the callback already, so use "?." here.
  85. client.OnDisconnected = () => OnClientDisconnected?.Invoke();
  86. // client configuration
  87. client.NoDelay = NoDelay;
  88. client.SendTimeout = SendTimeout;
  89. client.ReceiveTimeout = ReceiveTimeout;
  90. client.SendQueueLimit = clientSendQueueLimit;
  91. client.ReceiveQueueLimit = clientReceiveQueueLimit;
  92. }
  93. public override bool ClientConnected() => client != null && client.Connected;
  94. public override void ClientConnect(string address)
  95. {
  96. CreateClient();
  97. client.Connect(address, port);
  98. }
  99. public override void ClientConnect(Uri uri)
  100. {
  101. CreateClient();
  102. if (uri.Scheme != Scheme)
  103. throw new ArgumentException($"Invalid url {uri}, use {Scheme}://host:port instead", nameof(uri));
  104. int serverPort = uri.IsDefaultPort ? port : uri.Port;
  105. client.Connect(uri.Host, serverPort);
  106. }
  107. public override void ClientSend(ArraySegment<byte> segment, int channelId)
  108. {
  109. client?.Send(segment);
  110. // call event. might be null if no statistics are listening etc.
  111. OnClientDataSent?.Invoke(segment, Channels.Reliable);
  112. }
  113. public override void ClientDisconnect()
  114. {
  115. client?.Disconnect();
  116. client = null;
  117. // client triggers the disconnected event in client.Tick() which won't be run anymore
  118. OnClientDisconnected?.Invoke();
  119. }
  120. // messages should always be processed in early update
  121. public override void ClientEarlyUpdate()
  122. {
  123. // note: we need to check enabled in case we set it to false
  124. // when LateUpdate already started.
  125. // (https://github.com/vis2k/Mirror/pull/379)
  126. if (!enabled) return;
  127. // process a maximum amount of client messages per tick
  128. // IMPORTANT: check .enabled to stop processing immediately after a
  129. // scene change message arrives!
  130. client?.Tick(clientMaxReceivesPerTick, enabledCheck);
  131. }
  132. // server
  133. public override Uri ServerUri()
  134. {
  135. UriBuilder builder = new UriBuilder();
  136. builder.Scheme = Scheme;
  137. builder.Host = Dns.GetHostName();
  138. builder.Port = port;
  139. return builder.Uri;
  140. }
  141. public override bool ServerActive() => server != null && server.Active;
  142. public override void ServerStart()
  143. {
  144. // create server
  145. server = new Telepathy.Server(serverMaxMessageSize);
  146. // server hooks
  147. // other systems hook into transport events in OnCreate or
  148. // OnStartRunning in no particular order. the only way to avoid
  149. // race conditions where telepathy uses OnConnected before another
  150. // system's hook (e.g. statistics OnData) was added is to wrap
  151. // them all in a lambda and always call the latest hook.
  152. // (= lazy call)
  153. server.OnConnected = (connectionId) => OnServerConnected.Invoke(connectionId);
  154. server.OnData = (connectionId, segment) => OnServerDataReceived.Invoke(connectionId, segment, Channels.Reliable);
  155. server.OnDisconnected = (connectionId) => OnServerDisconnected.Invoke(connectionId);
  156. // server configuration
  157. server.NoDelay = NoDelay;
  158. server.SendTimeout = SendTimeout;
  159. server.ReceiveTimeout = ReceiveTimeout;
  160. server.SendQueueLimit = serverSendQueueLimitPerConnection;
  161. server.ReceiveQueueLimit = serverReceiveQueueLimitPerConnection;
  162. server.Start(port);
  163. }
  164. public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
  165. {
  166. server?.Send(connectionId, segment);
  167. // call event. might be null if no statistics are listening etc.
  168. OnServerDataSent?.Invoke(connectionId, segment, Channels.Reliable);
  169. }
  170. public override void ServerDisconnect(int connectionId) => server?.Disconnect(connectionId);
  171. public override string ServerGetClientAddress(int connectionId)
  172. {
  173. try
  174. {
  175. return server?.GetClientAddress(connectionId);
  176. }
  177. catch (SocketException)
  178. {
  179. // using server.listener.LocalEndpoint causes an Exception
  180. // in UWP + Unity 2019:
  181. // Exception thrown at 0x00007FF9755DA388 in UWF.exe:
  182. // Microsoft C++ exception: Il2CppExceptionWrapper at memory
  183. // location 0x000000E15A0FCDD0. SocketException: An address
  184. // incompatible with the requested protocol was used at
  185. // System.Net.Sockets.Socket.get_LocalEndPoint ()
  186. // so let's at least catch it and recover
  187. return "unknown";
  188. }
  189. }
  190. public override void ServerStop()
  191. {
  192. server?.Stop();
  193. server = null;
  194. }
  195. // messages should always be processed in early update
  196. public override void ServerEarlyUpdate()
  197. {
  198. // note: we need to check enabled in case we set it to false
  199. // when LateUpdate already started.
  200. // (https://github.com/vis2k/Mirror/pull/379)
  201. if (!enabled) return;
  202. // process a maximum amount of server messages per tick
  203. // IMPORTANT: check .enabled to stop processing immediately after a
  204. // scene change message arrives!
  205. server?.Tick(serverMaxReceivesPerTick, enabledCheck);
  206. }
  207. // common
  208. public override void Shutdown()
  209. {
  210. Debug.Log("TelepathyTransport Shutdown()");
  211. client?.Disconnect();
  212. client = null;
  213. server?.Stop();
  214. server = null;
  215. }
  216. public override int GetMaxPacketSize(int channelId)
  217. {
  218. return serverMaxMessageSize;
  219. }
  220. // Keep it short and simple so it looks nice in the HUD.
  221. //
  222. // printing server.listener.LocalEndpoint causes an Exception
  223. // in UWP + Unity 2019:
  224. // Exception thrown at 0x00007FF9755DA388 in UWF.exe:
  225. // Microsoft C++ exception: Il2CppExceptionWrapper at memory
  226. // location 0x000000E15A0FCDD0. SocketException: An address
  227. // incompatible with the requested protocol was used at
  228. // System.Net.Sockets.Socket.get_LocalEndPoint ()
  229. // so just use the regular port instead.
  230. public override string ToString() => $"Telepathy [{port}]";
  231. }
  232. }