MultiplexTransport.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. using System;
  2. using System.Text;
  3. using UnityEngine;
  4. namespace Mirror
  5. {
  6. // a transport that can listen to multiple underlying transport at the same time
  7. [DisallowMultipleComponent]
  8. public class MultiplexTransport : Transport
  9. {
  10. public Transport[] transports;
  11. Transport available;
  12. public void Awake()
  13. {
  14. if (transports == null || transports.Length == 0)
  15. {
  16. Debug.LogError("Multiplex transport requires at least 1 underlying transport");
  17. }
  18. }
  19. public override void ClientEarlyUpdate()
  20. {
  21. foreach (Transport transport in transports)
  22. {
  23. transport.ClientEarlyUpdate();
  24. }
  25. }
  26. public override void ServerEarlyUpdate()
  27. {
  28. foreach (Transport transport in transports)
  29. {
  30. transport.ServerEarlyUpdate();
  31. }
  32. }
  33. public override void ClientLateUpdate()
  34. {
  35. foreach (Transport transport in transports)
  36. {
  37. transport.ClientLateUpdate();
  38. }
  39. }
  40. public override void ServerLateUpdate()
  41. {
  42. foreach (Transport transport in transports)
  43. {
  44. transport.ServerLateUpdate();
  45. }
  46. }
  47. void OnEnable()
  48. {
  49. foreach (Transport transport in transports)
  50. {
  51. transport.enabled = true;
  52. }
  53. }
  54. void OnDisable()
  55. {
  56. foreach (Transport transport in transports)
  57. {
  58. transport.enabled = false;
  59. }
  60. }
  61. public override bool Available()
  62. {
  63. // available if any of the transports is available
  64. foreach (Transport transport in transports)
  65. {
  66. if (transport.Available())
  67. {
  68. return true;
  69. }
  70. }
  71. return false;
  72. }
  73. #region Client
  74. public override void ClientConnect(string address)
  75. {
  76. foreach (Transport transport in transports)
  77. {
  78. if (transport.Available())
  79. {
  80. available = transport;
  81. transport.OnClientConnected = OnClientConnected;
  82. transport.OnClientDataReceived = OnClientDataReceived;
  83. transport.OnClientError = OnClientError;
  84. transport.OnClientDisconnected = OnClientDisconnected;
  85. transport.ClientConnect(address);
  86. return;
  87. }
  88. }
  89. throw new ArgumentException("No transport suitable for this platform");
  90. }
  91. public override void ClientConnect(Uri uri)
  92. {
  93. foreach (Transport transport in transports)
  94. {
  95. if (transport.Available())
  96. {
  97. try
  98. {
  99. available = transport;
  100. transport.OnClientConnected = OnClientConnected;
  101. transport.OnClientDataReceived = OnClientDataReceived;
  102. transport.OnClientError = OnClientError;
  103. transport.OnClientDisconnected = OnClientDisconnected;
  104. transport.ClientConnect(uri);
  105. return;
  106. }
  107. catch (ArgumentException)
  108. {
  109. // transport does not support the schema, just move on to the next one
  110. }
  111. }
  112. }
  113. throw new ArgumentException("No transport suitable for this platform");
  114. }
  115. public override bool ClientConnected()
  116. {
  117. return (object)available != null && available.ClientConnected();
  118. }
  119. public override void ClientDisconnect()
  120. {
  121. if ((object)available != null)
  122. available.ClientDisconnect();
  123. }
  124. public override void ClientSend(ArraySegment<byte> segment, int channelId)
  125. {
  126. available.ClientSend(segment, channelId);
  127. }
  128. #endregion
  129. #region Server
  130. // connection ids get mapped to base transports
  131. // if we have 3 transports, then
  132. // transport 0 will produce connection ids [0, 3, 6, 9, ...]
  133. // transport 1 will produce connection ids [1, 4, 7, 10, ...]
  134. // transport 2 will produce connection ids [2, 5, 8, 11, ...]
  135. int FromBaseId(int transportId, int connectionId)
  136. {
  137. return connectionId * transports.Length + transportId;
  138. }
  139. int ToBaseId(int connectionId)
  140. {
  141. return connectionId / transports.Length;
  142. }
  143. int ToTransportId(int connectionId)
  144. {
  145. return connectionId % transports.Length;
  146. }
  147. void AddServerCallbacks()
  148. {
  149. // wire all the base transports to my events
  150. for (int i = 0; i < transports.Length; i++)
  151. {
  152. // this is required for the handlers, if I use i directly
  153. // then all the handlers will use the last i
  154. int locali = i;
  155. Transport transport = transports[i];
  156. transport.OnServerConnected = (baseConnectionId =>
  157. {
  158. OnServerConnected.Invoke(FromBaseId(locali, baseConnectionId));
  159. });
  160. transport.OnServerDataReceived = (baseConnectionId, data, channel) =>
  161. {
  162. OnServerDataReceived.Invoke(FromBaseId(locali, baseConnectionId), data, channel);
  163. };
  164. transport.OnServerError = (baseConnectionId, error, reason) =>
  165. {
  166. OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error, reason);
  167. };
  168. transport.OnServerDisconnected = baseConnectionId =>
  169. {
  170. OnServerDisconnected.Invoke(FromBaseId(locali, baseConnectionId));
  171. };
  172. }
  173. }
  174. // for now returns the first uri,
  175. // should we return all available uris?
  176. public override Uri ServerUri()
  177. {
  178. return transports[0].ServerUri();
  179. }
  180. public override bool ServerActive()
  181. {
  182. // avoid Linq.All allocations
  183. foreach (Transport transport in transports)
  184. {
  185. if (!transport.ServerActive())
  186. {
  187. return false;
  188. }
  189. }
  190. return true;
  191. }
  192. public override string ServerGetClientAddress(int connectionId)
  193. {
  194. int baseConnectionId = ToBaseId(connectionId);
  195. int transportId = ToTransportId(connectionId);
  196. return transports[transportId].ServerGetClientAddress(baseConnectionId);
  197. }
  198. public override void ServerDisconnect(int connectionId)
  199. {
  200. int baseConnectionId = ToBaseId(connectionId);
  201. int transportId = ToTransportId(connectionId);
  202. transports[transportId].ServerDisconnect(baseConnectionId);
  203. }
  204. public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
  205. {
  206. int baseConnectionId = ToBaseId(connectionId);
  207. int transportId = ToTransportId(connectionId);
  208. for (int i = 0; i < transports.Length; ++i)
  209. {
  210. if (i == transportId)
  211. {
  212. transports[i].ServerSend(baseConnectionId, segment, channelId);
  213. }
  214. }
  215. }
  216. public override void ServerStart()
  217. {
  218. foreach (Transport transport in transports)
  219. {
  220. AddServerCallbacks();
  221. transport.ServerStart();
  222. }
  223. }
  224. public override void ServerStop()
  225. {
  226. foreach (Transport transport in transports)
  227. {
  228. transport.ServerStop();
  229. }
  230. }
  231. #endregion
  232. public override int GetMaxPacketSize(int channelId = 0)
  233. {
  234. // finding the max packet size in a multiplex environment has to be
  235. // done very carefully:
  236. // * servers run multiple transports at the same time
  237. // * different clients run different transports
  238. // * there should only ever be ONE true max packet size for everyone,
  239. // otherwise a spawn message might be sent to all tcp sockets, but
  240. // be too big for some udp sockets. that would be a debugging
  241. // nightmare and allow for possible exploits and players on
  242. // different platforms seeing a different game state.
  243. // => the safest solution is to use the smallest max size for all
  244. // transports. that will never fail.
  245. int mininumAllowedSize = int.MaxValue;
  246. foreach (Transport transport in transports)
  247. {
  248. int size = transport.GetMaxPacketSize(channelId);
  249. mininumAllowedSize = Mathf.Min(size, mininumAllowedSize);
  250. }
  251. return mininumAllowedSize;
  252. }
  253. public override void Shutdown()
  254. {
  255. foreach (Transport transport in transports)
  256. {
  257. transport.Shutdown();
  258. }
  259. }
  260. public override string ToString()
  261. {
  262. StringBuilder builder = new StringBuilder();
  263. foreach (Transport transport in transports)
  264. {
  265. builder.AppendLine(transport.ToString());
  266. }
  267. return builder.ToString().Trim();
  268. }
  269. }
  270. }