TelepathyTransport.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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
  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. [Header("Common")]
  18. [Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")]
  19. public bool NoDelay = true;
  20. [Tooltip("Send timeout in milliseconds.")]
  21. public int SendTimeout = 5000;
  22. [Tooltip("Receive timeout in milliseconds. High by default so users don't time out during scene changes.")]
  23. public int ReceiveTimeout = 30000;
  24. [Header("Server")]
  25. [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.")]
  26. public int serverMaxMessageSize = 16 * 1024;
  27. [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.")]
  28. public int serverMaxReceivesPerTick = 10000;
  29. [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.")]
  30. public int serverSendQueueLimitPerConnection = 10000;
  31. [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.")]
  32. public int serverReceiveQueueLimitPerConnection = 10000;
  33. [Header("Client")]
  34. [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.")]
  35. public int clientMaxMessageSize = 16 * 1024;
  36. [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.")]
  37. public int clientMaxReceivesPerTick = 1000;
  38. [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.")]
  39. public int clientSendQueueLimit = 10000;
  40. [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.")]
  41. public int clientReceiveQueueLimit = 10000;
  42. Telepathy.Client client;
  43. Telepathy.Server server;
  44. // scene change message needs to halt message processing immediately
  45. // Telepathy.Tick() has a enabledCheck parameter that we can use, but
  46. // let's only allocate it once.
  47. Func<bool> enabledCheck;
  48. void Awake()
  49. {
  50. // tell Telepathy to use Unity's Debug.Log
  51. Telepathy.Log.Info = Debug.Log;
  52. Telepathy.Log.Warning = Debug.LogWarning;
  53. Telepathy.Log.Error = Debug.LogError;
  54. // allocate enabled check only once
  55. enabledCheck = () => enabled;
  56. Debug.Log("TelepathyTransport initialized!");
  57. }
  58. public override bool Available()
  59. {
  60. // C#'s built in TCP sockets run everywhere except on WebGL
  61. return Application.platform != RuntimePlatform.WebGLPlayer;
  62. }
  63. // client
  64. private void CreateClient()
  65. {
  66. // create client
  67. client = new Telepathy.Client(clientMaxMessageSize);
  68. // client hooks
  69. // other systems hook into transport events in OnCreate or
  70. // OnStartRunning in no particular order. the only way to avoid
  71. // race conditions where telepathy uses OnConnected before another
  72. // system's hook (e.g. statistics OnData) was added is to wrap
  73. // them all in a lambda and always call the latest hook.
  74. // (= lazy call)
  75. client.OnConnected = () => OnClientConnected.Invoke();
  76. client.OnData = (segment) => OnClientDataReceived.Invoke(segment, Channels.Reliable);
  77. client.OnDisconnected = () => OnClientDisconnected.Invoke();
  78. // client configuration
  79. client.NoDelay = NoDelay;
  80. client.SendTimeout = SendTimeout;
  81. client.ReceiveTimeout = ReceiveTimeout;
  82. client.SendQueueLimit = clientSendQueueLimit;
  83. client.ReceiveQueueLimit = clientReceiveQueueLimit;
  84. }
  85. public override bool ClientConnected() => client != null && client.Connected;
  86. public override void ClientConnect(string address)
  87. {
  88. CreateClient();
  89. client.Connect(address, port);
  90. }
  91. public override void ClientConnect(Uri uri)
  92. {
  93. CreateClient();
  94. if (uri.Scheme != Scheme)
  95. throw new ArgumentException($"Invalid url {uri}, use {Scheme}://host:port instead", nameof(uri));
  96. int serverPort = uri.IsDefaultPort ? port : uri.Port;
  97. client.Connect(uri.Host, serverPort);
  98. }
  99. public override void ClientSend(ArraySegment<byte> segment, int channelId) => client?.Send(segment);
  100. public override void ClientDisconnect()
  101. {
  102. client?.Disconnect();
  103. client = null;
  104. }
  105. // messages should always be processed in early update
  106. public override void ClientEarlyUpdate()
  107. {
  108. // note: we need to check enabled in case we set it to false
  109. // when LateUpdate already started.
  110. // (https://github.com/vis2k/Mirror/pull/379)
  111. if (!enabled) return;
  112. // process a maximum amount of client messages per tick
  113. // IMPORTANT: check .enabled to stop processing immediately after a
  114. // scene change message arrives!
  115. client?.Tick(clientMaxReceivesPerTick, enabledCheck);
  116. }
  117. // server
  118. public override Uri ServerUri()
  119. {
  120. UriBuilder builder = new UriBuilder();
  121. builder.Scheme = Scheme;
  122. builder.Host = Dns.GetHostName();
  123. builder.Port = port;
  124. return builder.Uri;
  125. }
  126. public override bool ServerActive() => server != null && server.Active;
  127. public override void ServerStart()
  128. {
  129. // create server
  130. server = new Telepathy.Server(serverMaxMessageSize);
  131. // server hooks
  132. // other systems hook into transport events in OnCreate or
  133. // OnStartRunning in no particular order. the only way to avoid
  134. // race conditions where telepathy uses OnConnected before another
  135. // system's hook (e.g. statistics OnData) was added is to wrap
  136. // them all in a lambda and always call the latest hook.
  137. // (= lazy call)
  138. server.OnConnected = (connectionId) => OnServerConnected.Invoke(connectionId);
  139. server.OnData = (connectionId, segment) => OnServerDataReceived.Invoke(connectionId, segment, Channels.Reliable);
  140. server.OnDisconnected = (connectionId) => OnServerDisconnected.Invoke(connectionId);
  141. // server configuration
  142. server.NoDelay = NoDelay;
  143. server.SendTimeout = SendTimeout;
  144. server.ReceiveTimeout = ReceiveTimeout;
  145. server.SendQueueLimit = serverSendQueueLimitPerConnection;
  146. server.ReceiveQueueLimit = serverReceiveQueueLimitPerConnection;
  147. server.Start(port);
  148. }
  149. public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId) => server?.Send(connectionId, segment);
  150. public override void ServerDisconnect(int connectionId) => server?.Disconnect(connectionId);
  151. public override string ServerGetClientAddress(int connectionId)
  152. {
  153. try
  154. {
  155. return server?.GetClientAddress(connectionId);
  156. }
  157. catch (SocketException)
  158. {
  159. // using server.listener.LocalEndpoint causes an Exception
  160. // in UWP + Unity 2019:
  161. // Exception thrown at 0x00007FF9755DA388 in UWF.exe:
  162. // Microsoft C++ exception: Il2CppExceptionWrapper at memory
  163. // location 0x000000E15A0FCDD0. SocketException: An address
  164. // incompatible with the requested protocol was used at
  165. // System.Net.Sockets.Socket.get_LocalEndPoint ()
  166. // so let's at least catch it and recover
  167. return "unknown";
  168. }
  169. }
  170. public override void ServerStop()
  171. {
  172. server?.Stop();
  173. server = null;
  174. }
  175. // messages should always be processed in early update
  176. public override void ServerEarlyUpdate()
  177. {
  178. // note: we need to check enabled in case we set it to false
  179. // when LateUpdate already started.
  180. // (https://github.com/vis2k/Mirror/pull/379)
  181. if (!enabled) return;
  182. // process a maximum amount of server messages per tick
  183. // IMPORTANT: check .enabled to stop processing immediately after a
  184. // scene change message arrives!
  185. server?.Tick(serverMaxReceivesPerTick, enabledCheck);
  186. }
  187. // common
  188. public override void Shutdown()
  189. {
  190. Debug.Log("TelepathyTransport Shutdown()");
  191. client?.Disconnect();
  192. client = null;
  193. server?.Stop();
  194. server = null;
  195. }
  196. public override int GetMaxPacketSize(int channelId)
  197. {
  198. return serverMaxMessageSize;
  199. }
  200. public override string ToString()
  201. {
  202. if (server != null && server.Active && server.listener != null)
  203. {
  204. // printing server.listener.LocalEndpoint causes an Exception
  205. // in UWP + Unity 2019:
  206. // Exception thrown at 0x00007FF9755DA388 in UWF.exe:
  207. // Microsoft C++ exception: Il2CppExceptionWrapper at memory
  208. // location 0x000000E15A0FCDD0. SocketException: An address
  209. // incompatible with the requested protocol was used at
  210. // System.Net.Sockets.Socket.get_LocalEndPoint ()
  211. // so let's use the regular port instead.
  212. return $"Telepathy Server port: {port}";
  213. }
  214. else if (client != null && (client.Connecting || client.Connected))
  215. {
  216. return $"Telepathy Client port: {port}";
  217. }
  218. return "Telepathy (inactive/disconnected)";
  219. }
  220. }
  221. }