SimpleWebTransport.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. using System;
  2. using System.Net;
  3. using System.Security.Authentication;
  4. using UnityEngine;
  5. using UnityEngine.Serialization;
  6. namespace Mirror.SimpleWeb
  7. {
  8. [DisallowMultipleComponent]
  9. public class SimpleWebTransport : Transport
  10. {
  11. public const string NormalScheme = "ws";
  12. public const string SecureScheme = "wss";
  13. [Tooltip("Port to use for server and client")]
  14. public ushort port = 7778;
  15. [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.")]
  16. public int maxMessageSize = 16 * 1024;
  17. [Tooltip("Max size for http header send as handshake for websockets")]
  18. public int handshakeMaxSize = 3000;
  19. [Tooltip("disables nagle algorithm. lowers CPU% and latency but increases bandwidth")]
  20. public bool noDelay = true;
  21. [Tooltip("Send would stall forever if the network is cut off during a send, so we need a timeout (in milliseconds)")]
  22. public int sendTimeout = 5000;
  23. [Tooltip("How long without a message before disconnecting (in milliseconds)")]
  24. public int receiveTimeout = 20000;
  25. [Tooltip("Caps the number of messages the server will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")]
  26. public int serverMaxMessagesPerTick = 10000;
  27. [Tooltip("Caps the number of messages the client will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")]
  28. public int clientMaxMessagesPerTick = 1000;
  29. [Header("Server settings")]
  30. [Tooltip("Groups messages in queue before calling Stream.Send")]
  31. public bool batchSend = true;
  32. [Tooltip("Waits for 1ms before grouping and sending messages. " +
  33. "This gives time for mirror to finish adding message to queue so that less groups need to be made. " +
  34. "If WaitBeforeSend is true then BatchSend Will also be set to true")]
  35. public bool waitBeforeSend = false;
  36. [Header("Ssl Settings")]
  37. [Tooltip("Sets connect scheme to wss. Useful when client needs to connect using wss when TLS is outside of transport, NOTE: if sslEnabled is true clientUseWss is also true")]
  38. public bool clientUseWss;
  39. public bool sslEnabled;
  40. [Tooltip("Path to json file that contains path to cert and its password\n\nUse Json file so that cert password is not included in client builds\n\nSee cert.example.Json")]
  41. public string sslCertJson = "./cert.json";
  42. public SslProtocols sslProtocols = SslProtocols.Tls12;
  43. [Header("Debug")]
  44. [Tooltip("Log functions uses ConditionalAttribute which will effect which log methods are allowed. DEBUG allows warn/error, SIMPLEWEB_LOG_ENABLED allows all")]
  45. [FormerlySerializedAs("logLevels")]
  46. [SerializeField] Log.Levels _logLevels = Log.Levels.none;
  47. /// <summary>
  48. /// <para>Gets _logLevels field</para>
  49. /// <para>Sets _logLevels and Log.level fields</para>
  50. /// </summary>
  51. public Log.Levels LogLevels
  52. {
  53. get => _logLevels;
  54. set
  55. {
  56. _logLevels = value;
  57. Log.level = _logLevels;
  58. }
  59. }
  60. void OnValidate()
  61. {
  62. Log.level = _logLevels;
  63. }
  64. SimpleWebClient client;
  65. SimpleWebServer server;
  66. TcpConfig TcpConfig => new TcpConfig(noDelay, sendTimeout, receiveTimeout);
  67. public override bool Available()
  68. {
  69. return true;
  70. }
  71. public override int GetMaxPacketSize(int channelId = 0)
  72. {
  73. return maxMessageSize;
  74. }
  75. void Awake()
  76. {
  77. Log.level = _logLevels;
  78. }
  79. public override void Shutdown()
  80. {
  81. client?.Disconnect();
  82. client = null;
  83. server?.Stop();
  84. server = null;
  85. }
  86. #region Client
  87. string GetClientScheme() => (sslEnabled || clientUseWss) ? SecureScheme : NormalScheme;
  88. string GetServerScheme() => sslEnabled ? SecureScheme : NormalScheme;
  89. public override bool ClientConnected()
  90. {
  91. // not null and not NotConnected (we want to return true if connecting or disconnecting)
  92. return client != null && client.ConnectionState != ClientState.NotConnected;
  93. }
  94. public override void ClientConnect(string hostname)
  95. {
  96. // connecting or connected
  97. if (ClientConnected())
  98. {
  99. Debug.LogError("Already Connected");
  100. return;
  101. }
  102. UriBuilder builder = new UriBuilder
  103. {
  104. Scheme = GetClientScheme(),
  105. Host = hostname,
  106. Port = port
  107. };
  108. client = SimpleWebClient.Create(maxMessageSize, clientMaxMessagesPerTick, TcpConfig);
  109. if (client == null) { return; }
  110. client.onConnect += OnClientConnected.Invoke;
  111. client.onDisconnect += () =>
  112. {
  113. OnClientDisconnected.Invoke();
  114. // clear client here after disconnect event has been sent
  115. // there should be no more messages after disconnect
  116. client = null;
  117. };
  118. client.onData += (ArraySegment<byte> data) => OnClientDataReceived.Invoke(data, Channels.Reliable);
  119. client.onError += (Exception e) =>
  120. {
  121. OnClientError.Invoke(TransportError.Unexpected, e.ToString());
  122. ClientDisconnect();
  123. };
  124. client.Connect(builder.Uri);
  125. }
  126. public override void ClientDisconnect()
  127. {
  128. // don't set client null here of messages wont be processed
  129. client?.Disconnect();
  130. }
  131. public override void ClientSend(ArraySegment<byte> segment, int channelId)
  132. {
  133. if (!ClientConnected())
  134. {
  135. Debug.LogError("Not Connected");
  136. return;
  137. }
  138. if (segment.Count > maxMessageSize)
  139. {
  140. Log.Error("Message greater than max size");
  141. return;
  142. }
  143. if (segment.Count == 0)
  144. {
  145. Log.Error("Message count was zero");
  146. return;
  147. }
  148. client.Send(segment);
  149. // call event. might be null if no statistics are listening etc.
  150. OnClientDataSent?.Invoke(segment, Channels.Reliable);
  151. }
  152. // messages should always be processed in early update
  153. public override void ClientEarlyUpdate()
  154. {
  155. client?.ProcessMessageQueue(this);
  156. }
  157. #endregion
  158. #region Server
  159. public override bool ServerActive()
  160. {
  161. return server != null && server.Active;
  162. }
  163. public override void ServerStart()
  164. {
  165. if (ServerActive())
  166. {
  167. Debug.LogError("SimpleWebServer Already Started");
  168. }
  169. SslConfig config = SslConfigLoader.Load(sslEnabled, sslCertJson, sslProtocols);
  170. server = new SimpleWebServer(serverMaxMessagesPerTick, TcpConfig, maxMessageSize, handshakeMaxSize, config);
  171. server.onConnect += OnServerConnected.Invoke;
  172. server.onDisconnect += OnServerDisconnected.Invoke;
  173. server.onData += (int connId, ArraySegment<byte> data) => OnServerDataReceived.Invoke(connId, data, Channels.Reliable);
  174. server.onError += (connId, exception) => OnServerError(connId, TransportError.Unexpected, exception.ToString());
  175. SendLoopConfig.batchSend = batchSend || waitBeforeSend;
  176. SendLoopConfig.sleepBeforeSend = waitBeforeSend;
  177. server.Start(port);
  178. }
  179. public override void ServerStop()
  180. {
  181. if (!ServerActive())
  182. {
  183. Debug.LogError("SimpleWebServer Not Active");
  184. }
  185. server.Stop();
  186. server = null;
  187. }
  188. public override void ServerDisconnect(int connectionId)
  189. {
  190. if (!ServerActive())
  191. {
  192. Debug.LogError("SimpleWebServer Not Active");
  193. }
  194. server.KickClient(connectionId);
  195. }
  196. public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
  197. {
  198. if (!ServerActive())
  199. {
  200. Debug.LogError("SimpleWebServer Not Active");
  201. return;
  202. }
  203. if (segment.Count > maxMessageSize)
  204. {
  205. Log.Error("Message greater than max size");
  206. return;
  207. }
  208. if (segment.Count == 0)
  209. {
  210. Log.Error("Message count was zero");
  211. return;
  212. }
  213. server.SendOne(connectionId, segment);
  214. // call event. might be null if no statistics are listening etc.
  215. OnServerDataSent?.Invoke(connectionId, segment, Channels.Reliable);
  216. }
  217. public override string ServerGetClientAddress(int connectionId)
  218. {
  219. return server.GetClientAddress(connectionId);
  220. }
  221. public override Uri ServerUri()
  222. {
  223. UriBuilder builder = new UriBuilder
  224. {
  225. Scheme = GetServerScheme(),
  226. Host = Dns.GetHostName(),
  227. Port = port
  228. };
  229. return builder.Uri;
  230. }
  231. // messages should always be processed in early update
  232. public override void ServerEarlyUpdate()
  233. {
  234. server?.ProcessMessageQueue(this);
  235. }
  236. #endregion
  237. }
  238. }