MultiplexTransport.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using UnityEngine;
  5. namespace Mirror
  6. {
  7. // a transport that can listen to multiple underlying transport at the same time
  8. [DisallowMultipleComponent]
  9. public class MultiplexTransport : Transport, PortTransport
  10. {
  11. public Transport[] transports;
  12. Transport available;
  13. // underlying transport connectionId to multiplexed connectionId lookup.
  14. //
  15. // originally we used a formula to map the connectionId:
  16. // connectionId * transportAmount + transportId
  17. //
  18. // if we have 3 transports, then
  19. // transport 0 will produce connection ids [0, 3, 6, 9, ...]
  20. // transport 1 will produce connection ids [1, 4, 7, 10, ...]
  21. // transport 2 will produce connection ids [2, 5, 8, 11, ...]
  22. //
  23. // however, some transports like kcp may give very large connectionIds.
  24. // if they are near int.max, then "* transprotAmount + transportIndex"
  25. // will overflow, resulting in connIds which can't be projected back.
  26. // https://github.com/vis2k/Mirror/issues/3280
  27. //
  28. // instead, use a simple lookup with 0-indexed ids.
  29. // with initial capacity to avoid runtime allocations.
  30. // (original connectionId, transport#) to multiplexed connectionId
  31. readonly Dictionary<KeyValuePair<int, int>, int> originalToMultiplexedId =
  32. new Dictionary<KeyValuePair<int, int>, int>(100);
  33. // multiplexed connectionId to (original connectionId, transport#)
  34. readonly Dictionary<int, KeyValuePair<int, int>> multiplexedToOriginalId =
  35. new Dictionary<int, KeyValuePair<int, int>>(100);
  36. // next multiplexed id counter. start at 1 because 0 is reserved for host.
  37. int nextMultiplexedId = 1;
  38. // prevent log flood from OnGUI or similar per-frame updates
  39. bool alreadyWarned;
  40. public ushort Port
  41. {
  42. get
  43. {
  44. foreach (Transport transport in transports)
  45. if (transport.Available() && transport is PortTransport portTransport)
  46. return portTransport.Port;
  47. return 0;
  48. }
  49. set
  50. {
  51. if (Utils.IsHeadless() && !alreadyWarned)
  52. {
  53. // prevent log flood from OnGUI or similar per-frame updates
  54. alreadyWarned = true;
  55. Debug.LogWarning($"MultiplexTransport: Server cannot set the same listen port for all transports! Set them directly instead.");
  56. }
  57. else
  58. {
  59. // We can't set the same port for all transports because
  60. // listen ports have to be different for each transport
  61. // so we just set the first available one.
  62. // This depends on the selected build platform.
  63. foreach (Transport transport in transports)
  64. if (transport.Available() && transport is PortTransport portTransport)
  65. {
  66. portTransport.Port = value;
  67. break;
  68. }
  69. }
  70. }
  71. }
  72. // add to bidirection lookup. returns the multiplexed connectionId.
  73. public int AddToLookup(int originalConnectionId, int transportIndex)
  74. {
  75. // add to both
  76. KeyValuePair<int, int> pair = new KeyValuePair<int, int>(originalConnectionId, transportIndex);
  77. int multiplexedId = nextMultiplexedId++;
  78. originalToMultiplexedId[pair] = multiplexedId;
  79. multiplexedToOriginalId[multiplexedId] = pair;
  80. return multiplexedId;
  81. }
  82. public void RemoveFromLookup(int originalConnectionId, int transportIndex)
  83. {
  84. // remove from both
  85. KeyValuePair<int, int> pair = new KeyValuePair<int, int>(originalConnectionId, transportIndex);
  86. int multiplexedId = originalToMultiplexedId[pair];
  87. originalToMultiplexedId.Remove(pair);
  88. multiplexedToOriginalId.Remove(multiplexedId);
  89. }
  90. public void OriginalId(int multiplexId, out int originalConnectionId, out int transportIndex)
  91. {
  92. KeyValuePair<int, int> pair = multiplexedToOriginalId[multiplexId];
  93. originalConnectionId = pair.Key;
  94. transportIndex = pair.Value;
  95. }
  96. public int MultiplexId(int originalConnectionId, int transportIndex)
  97. {
  98. KeyValuePair<int, int> pair = new KeyValuePair<int, int>(originalConnectionId, transportIndex);
  99. return originalToMultiplexedId[pair];
  100. }
  101. ////////////////////////////////////////////////////////////////////////
  102. public void Awake()
  103. {
  104. if (transports == null || transports.Length == 0)
  105. {
  106. Debug.LogError("[Multiplexer] Multiplex transport requires at least 1 underlying transport");
  107. }
  108. }
  109. public override void ClientEarlyUpdate()
  110. {
  111. foreach (Transport transport in transports)
  112. transport.ClientEarlyUpdate();
  113. }
  114. public override void ServerEarlyUpdate()
  115. {
  116. foreach (Transport transport in transports)
  117. transport.ServerEarlyUpdate();
  118. }
  119. public override void ClientLateUpdate()
  120. {
  121. foreach (Transport transport in transports)
  122. transport.ClientLateUpdate();
  123. }
  124. public override void ServerLateUpdate()
  125. {
  126. foreach (Transport transport in transports)
  127. transport.ServerLateUpdate();
  128. }
  129. void OnEnable()
  130. {
  131. foreach (Transport transport in transports)
  132. transport.enabled = true;
  133. }
  134. void OnDisable()
  135. {
  136. foreach (Transport transport in transports)
  137. transport.enabled = false;
  138. }
  139. public override bool Available()
  140. {
  141. // available if any of the transports is available
  142. foreach (Transport transport in transports)
  143. if (transport.Available())
  144. return true;
  145. return false;
  146. }
  147. #region Client
  148. public override void ClientConnect(string address)
  149. {
  150. foreach (Transport transport in transports)
  151. {
  152. if (transport.Available())
  153. {
  154. available = transport;
  155. transport.OnClientConnected = OnClientConnected;
  156. transport.OnClientDataReceived = OnClientDataReceived;
  157. transport.OnClientError = OnClientError;
  158. transport.OnClientDisconnected = OnClientDisconnected;
  159. transport.ClientConnect(address);
  160. return;
  161. }
  162. }
  163. throw new ArgumentException("[Multiplexer] No transport suitable for this platform");
  164. }
  165. public override void ClientConnect(Uri uri)
  166. {
  167. foreach (Transport transport in transports)
  168. {
  169. if (transport.Available())
  170. {
  171. try
  172. {
  173. available = transport;
  174. transport.OnClientConnected = OnClientConnected;
  175. transport.OnClientDataReceived = OnClientDataReceived;
  176. transport.OnClientError = OnClientError;
  177. transport.OnClientDisconnected = OnClientDisconnected;
  178. transport.ClientConnect(uri);
  179. return;
  180. }
  181. catch (ArgumentException)
  182. {
  183. // transport does not support the schema, just move on to the next one
  184. }
  185. }
  186. }
  187. throw new ArgumentException("[Multiplexer] No transport suitable for this platform");
  188. }
  189. public override bool ClientConnected()
  190. {
  191. return (object)available != null && available.ClientConnected();
  192. }
  193. public override void ClientDisconnect()
  194. {
  195. if ((object)available != null)
  196. available.ClientDisconnect();
  197. }
  198. public override void ClientSend(ArraySegment<byte> segment, int channelId)
  199. {
  200. available.ClientSend(segment, channelId);
  201. }
  202. #endregion
  203. #region Server
  204. void AddServerCallbacks()
  205. {
  206. // all underlying transports should call the multiplex transport's events
  207. for (int i = 0; i < transports.Length; i++)
  208. {
  209. // this is required for the handlers, if I use i directly
  210. // then all the handlers will use the last i
  211. int transportIndex = i;
  212. Transport transport = transports[i];
  213. transport.OnServerConnected = (originalConnectionId =>
  214. {
  215. // invoke Multiplex event with multiplexed connectionId
  216. int multiplexedId = AddToLookup(originalConnectionId, transportIndex);
  217. OnServerConnected.Invoke(multiplexedId);
  218. });
  219. transport.OnServerDataReceived = (originalConnectionId, data, channel) =>
  220. {
  221. // invoke Multiplex event with multiplexed connectionId
  222. int multiplexedId = MultiplexId(originalConnectionId, transportIndex);
  223. OnServerDataReceived.Invoke(multiplexedId, data, channel);
  224. };
  225. transport.OnServerError = (originalConnectionId, error, reason) =>
  226. {
  227. // invoke Multiplex event with multiplexed connectionId
  228. int multiplexedId = MultiplexId(originalConnectionId, transportIndex);
  229. OnServerError.Invoke(multiplexedId, error, reason);
  230. };
  231. transport.OnServerDisconnected = originalConnectionId =>
  232. {
  233. // invoke Multiplex event with multiplexed connectionId
  234. int multiplexedId = MultiplexId(originalConnectionId, transportIndex);
  235. OnServerDisconnected.Invoke(multiplexedId);
  236. RemoveFromLookup(originalConnectionId, transportIndex);
  237. };
  238. }
  239. }
  240. // for now returns the first uri,
  241. // should we return all available uris?
  242. public override Uri ServerUri() =>
  243. transports[0].ServerUri();
  244. public override bool ServerActive()
  245. {
  246. // avoid Linq.All allocations
  247. foreach (Transport transport in transports)
  248. if (!transport.ServerActive())
  249. return false;
  250. return true;
  251. }
  252. public override string ServerGetClientAddress(int connectionId)
  253. {
  254. // convert multiplexed connectionId to original id & transport index
  255. OriginalId(connectionId, out int originalConnectionId, out int transportIndex);
  256. return transports[transportIndex].ServerGetClientAddress(originalConnectionId);
  257. }
  258. public override void ServerDisconnect(int connectionId)
  259. {
  260. // convert multiplexed connectionId to original id & transport index
  261. OriginalId(connectionId, out int originalConnectionId, out int transportIndex);
  262. transports[transportIndex].ServerDisconnect(originalConnectionId);
  263. }
  264. public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
  265. {
  266. // convert multiplexed connectionId to original transport + connId
  267. OriginalId(connectionId, out int originalConnectionId, out int transportIndex);
  268. transports[transportIndex].ServerSend(originalConnectionId, segment, channelId);
  269. }
  270. public override void ServerStart()
  271. {
  272. AddServerCallbacks();
  273. foreach (Transport transport in transports)
  274. {
  275. transport.ServerStart();
  276. if (transport is PortTransport portTransport)
  277. {
  278. if (Utils.IsHeadless())
  279. {
  280. Console.ForegroundColor = ConsoleColor.Green;
  281. Console.WriteLine($"Server listening on port {portTransport.Port}");
  282. Console.ResetColor();
  283. }
  284. else
  285. {
  286. Debug.Log($"Server listening on port {portTransport.Port}");
  287. }
  288. }
  289. }
  290. }
  291. public override void ServerStop()
  292. {
  293. foreach (Transport transport in transports)
  294. transport.ServerStop();
  295. }
  296. #endregion
  297. public override int GetMaxPacketSize(int channelId = 0)
  298. {
  299. // finding the max packet size in a multiplex environment has to be
  300. // done very carefully:
  301. // * servers run multiple transports at the same time
  302. // * different clients run different transports
  303. // * there should only ever be ONE true max packet size for everyone,
  304. // otherwise a spawn message might be sent to all tcp sockets, but
  305. // be too big for some udp sockets. that would be a debugging
  306. // nightmare and allow for possible exploits and players on
  307. // different platforms seeing a different game state.
  308. // => the safest solution is to use the smallest max size for all
  309. // transports. that will never fail.
  310. int mininumAllowedSize = int.MaxValue;
  311. foreach (Transport transport in transports)
  312. {
  313. int size = transport.GetMaxPacketSize(channelId);
  314. mininumAllowedSize = Mathf.Min(size, mininumAllowedSize);
  315. }
  316. return mininumAllowedSize;
  317. }
  318. public override void Shutdown()
  319. {
  320. foreach (Transport transport in transports)
  321. transport.Shutdown();
  322. }
  323. public override string ToString()
  324. {
  325. StringBuilder builder = new StringBuilder();
  326. builder.Append("Multiplexer:");
  327. foreach (Transport transport in transports)
  328. builder.Append($" {transport}");
  329. return builder.ToString().Trim();
  330. }
  331. }
  332. }