KcpTransport.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. //#if MIRROR <- commented out because MIRROR isn't defined on first import yet
  2. using System;
  3. using System.Linq;
  4. using System.Net;
  5. using UnityEngine;
  6. using Mirror;
  7. using Unity.Collections;
  8. using UnityEngine.Serialization;
  9. namespace kcp2k
  10. {
  11. [HelpURL("https://mirror-networking.gitbook.io/docs/transports/kcp-transport")]
  12. [DisallowMultipleComponent]
  13. public class KcpTransport : Transport, PortTransport
  14. {
  15. // scheme used by this transport
  16. public const string Scheme = "kcp";
  17. // common
  18. [Header("Transport Configuration")]
  19. [FormerlySerializedAs("Port")]
  20. public ushort port = 7777;
  21. public ushort Port { get => port; set => port=value; }
  22. [Tooltip("DualMode listens to IPv6 and IPv4 simultaneously. Disable if the platform only supports IPv4.")]
  23. public bool DualMode = true;
  24. [Tooltip("NoDelay is recommended to reduce latency. This also scales better without buffers getting full.")]
  25. public bool NoDelay = true;
  26. [Tooltip("KCP internal update interval. 100ms is KCP default, but a lower interval is recommended to minimize latency and to scale to more networked entities.")]
  27. public uint Interval = 10;
  28. [Tooltip("KCP timeout in milliseconds. Note that KCP sends a ping automatically.")]
  29. public int Timeout = 10000;
  30. [Tooltip("Socket receive buffer size. Large buffer helps support more connections. Increase operating system socket buffer size limits if needed.")]
  31. public int RecvBufferSize = 1024 * 1027 * 7;
  32. [Tooltip("Socket send buffer size. Large buffer helps support more connections. Increase operating system socket buffer size limits if needed.")]
  33. public int SendBufferSize = 1024 * 1027 * 7;
  34. [Header("Advanced")]
  35. [Tooltip("KCP fastresend parameter. Faster resend for the cost of higher bandwidth. 0 in normal mode, 2 in turbo mode.")]
  36. public int FastResend = 2;
  37. [Tooltip("KCP congestion window. Restricts window size to reduce congestion. Results in only 2-3 MTU messages per Flush even on loopback. Best to keept his disabled.")]
  38. /*public*/ bool CongestionWindow = false; // KCP 'NoCongestionWindow' is false by default. here we negate it for ease of use.
  39. [Tooltip("KCP window size can be modified to support higher loads. This also increases max message size.")]
  40. public uint ReceiveWindowSize = 4096; //Kcp.WND_RCV; 128 by default. Mirror sends a lot, so we need a lot more.
  41. [Tooltip("KCP window size can be modified to support higher loads.")]
  42. public uint SendWindowSize = 4096; //Kcp.WND_SND; 32 by default. Mirror sends a lot, so we need a lot more.
  43. [Tooltip("KCP will try to retransmit lost messages up to MaxRetransmit (aka dead_link) before disconnecting.")]
  44. public uint MaxRetransmit = Kcp.DEADLINK * 2; // default prematurely disconnects a lot of people (#3022). use 2x.
  45. [Tooltip("Enable to automatically set client & server send/recv buffers to OS limit. Avoids issues with too small buffers under heavy load, potentially dropping connections. Increase the OS limit if this is still too small.")]
  46. [FormerlySerializedAs("MaximizeSendReceiveBuffersToOSLimit")]
  47. public bool MaximizeSocketBuffers = true;
  48. [Header("Allowed Max Message Sizes\nBased on Receive Window Size")]
  49. [Tooltip("KCP reliable max message size shown for convenience. Can be changed via ReceiveWindowSize.")]
  50. [ReadOnly] public int ReliableMaxMessageSize = 0; // readonly, displayed from OnValidate
  51. [Tooltip("KCP unreliable channel max message size for convenience. Not changeable.")]
  52. [ReadOnly] public int UnreliableMaxMessageSize = 0; // readonly, displayed from OnValidate
  53. // config is created from the serialized properties above.
  54. // we can expose the config directly in the future.
  55. // for now, let's not break people's old settings.
  56. protected KcpConfig config;
  57. // use default MTU for this transport.
  58. const int MTU = Kcp.MTU_DEF;
  59. // server & client
  60. protected KcpServer server;
  61. protected KcpClient client;
  62. // debugging
  63. [Header("Debug")]
  64. public bool debugLog;
  65. // show statistics in OnGUI
  66. public bool statisticsGUI;
  67. // log statistics for headless servers that can't show them in GUI
  68. public bool statisticsLog;
  69. // translate Kcp <-> Mirror channels
  70. public static int FromKcpChannel(KcpChannel channel) =>
  71. channel == KcpChannel.Reliable ? Channels.Reliable : Channels.Unreliable;
  72. public static KcpChannel ToKcpChannel(int channel) =>
  73. channel == Channels.Reliable ? KcpChannel.Reliable : KcpChannel.Unreliable;
  74. public static TransportError ToTransportError(ErrorCode error)
  75. {
  76. switch(error)
  77. {
  78. case ErrorCode.DnsResolve: return TransportError.DnsResolve;
  79. case ErrorCode.Timeout: return TransportError.Timeout;
  80. case ErrorCode.Congestion: return TransportError.Congestion;
  81. case ErrorCode.InvalidReceive: return TransportError.InvalidReceive;
  82. case ErrorCode.InvalidSend: return TransportError.InvalidSend;
  83. case ErrorCode.ConnectionClosed: return TransportError.ConnectionClosed;
  84. case ErrorCode.Unexpected: return TransportError.Unexpected;
  85. default: throw new InvalidCastException($"KCP: missing error translation for {error}");
  86. }
  87. }
  88. protected virtual void Awake()
  89. {
  90. // logging
  91. // Log.Info should use Debug.Log if enabled, or nothing otherwise
  92. // (don't want to spam the console on headless servers)
  93. if (debugLog)
  94. Log.Info = Debug.Log;
  95. else
  96. Log.Info = _ => {};
  97. Log.Warning = Debug.LogWarning;
  98. Log.Error = Debug.LogError;
  99. // create config from serialized settings
  100. config = new KcpConfig(DualMode, RecvBufferSize, SendBufferSize, MTU, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout, MaxRetransmit);
  101. // client (NonAlloc version is not necessary anymore)
  102. client = new KcpClient(
  103. () => OnClientConnected.Invoke(),
  104. (message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)),
  105. () => OnClientDisconnected.Invoke(),
  106. (error, reason) => OnClientError.Invoke(ToTransportError(error), reason),
  107. config
  108. );
  109. // server
  110. server = new KcpServer(
  111. (connectionId) => OnServerConnected.Invoke(connectionId),
  112. (connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)),
  113. (connectionId) => OnServerDisconnected.Invoke(connectionId),
  114. (connectionId, error, reason) => OnServerError.Invoke(connectionId, ToTransportError(error), reason),
  115. config
  116. );
  117. if (statisticsLog)
  118. InvokeRepeating(nameof(OnLogStatistics), 1, 1);
  119. Log.Info("KcpTransport initialized!");
  120. }
  121. protected virtual void OnValidate()
  122. {
  123. // show max message sizes in inspector for convenience.
  124. // 'config' isn't available in edit mode yet, so use MTU define.
  125. ReliableMaxMessageSize = KcpPeer.ReliableMaxMessageSize(MTU, ReceiveWindowSize);
  126. UnreliableMaxMessageSize = KcpPeer.UnreliableMaxMessageSize(MTU);
  127. }
  128. // all except WebGL
  129. // Do not change this back to using Application.platform
  130. // because that doesn't work in the Editor!
  131. public override bool Available() =>
  132. #if UNITY_WEBGL
  133. false;
  134. #else
  135. true;
  136. #endif
  137. // client
  138. public override bool ClientConnected() => client.connected;
  139. public override void ClientConnect(string address)
  140. {
  141. client.Connect(address, Port);
  142. }
  143. public override void ClientConnect(Uri uri)
  144. {
  145. if (uri.Scheme != Scheme)
  146. throw new ArgumentException($"Invalid url {uri}, use {Scheme}://host:port instead", nameof(uri));
  147. int serverPort = uri.IsDefaultPort ? Port : uri.Port;
  148. client.Connect(uri.Host, (ushort)serverPort);
  149. }
  150. public override void ClientSend(ArraySegment<byte> segment, int channelId)
  151. {
  152. client.Send(segment, ToKcpChannel(channelId));
  153. // call event. might be null if no statistics are listening etc.
  154. OnClientDataSent?.Invoke(segment, channelId);
  155. }
  156. public override void ClientDisconnect() => client.Disconnect();
  157. // process incoming in early update
  158. public override void ClientEarlyUpdate()
  159. {
  160. // only process messages while transport is enabled.
  161. // scene change messsages disable it to stop processing.
  162. // (see also: https://github.com/vis2k/Mirror/pull/379)
  163. if (enabled) client.TickIncoming();
  164. }
  165. // process outgoing in late update
  166. public override void ClientLateUpdate() => client.TickOutgoing();
  167. // server
  168. public override Uri ServerUri()
  169. {
  170. UriBuilder builder = new UriBuilder();
  171. builder.Scheme = Scheme;
  172. builder.Host = Dns.GetHostName();
  173. builder.Port = Port;
  174. return builder.Uri;
  175. }
  176. public override bool ServerActive() => server.IsActive();
  177. public override void ServerStart() => server.Start(Port);
  178. public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
  179. {
  180. server.Send(connectionId, segment, ToKcpChannel(channelId));
  181. // call event. might be null if no statistics are listening etc.
  182. OnServerDataSent?.Invoke(connectionId, segment, channelId);
  183. }
  184. public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
  185. public override string ServerGetClientAddress(int connectionId)
  186. {
  187. IPEndPoint endPoint = server.GetClientEndPoint(connectionId);
  188. return endPoint != null
  189. // Map to IPv4 if "IsIPv4MappedToIPv6"
  190. // "::ffff:127.0.0.1" -> "127.0.0.1"
  191. ? (endPoint.Address.IsIPv4MappedToIPv6
  192. ? endPoint.Address.MapToIPv4().ToString()
  193. : endPoint.Address.ToString())
  194. : "";
  195. }
  196. public override void ServerStop() => server.Stop();
  197. public override void ServerEarlyUpdate()
  198. {
  199. // only process messages while transport is enabled.
  200. // scene change messsages disable it to stop processing.
  201. // (see also: https://github.com/vis2k/Mirror/pull/379)
  202. if (enabled) server.TickIncoming();
  203. }
  204. // process outgoing in late update
  205. public override void ServerLateUpdate() => server.TickOutgoing();
  206. // common
  207. public override void Shutdown() {}
  208. // max message size
  209. public override int GetMaxPacketSize(int channelId = Channels.Reliable)
  210. {
  211. // switch to kcp channel.
  212. // unreliable or reliable.
  213. // default to reliable just to be sure.
  214. switch (channelId)
  215. {
  216. case Channels.Unreliable:
  217. return KcpPeer.UnreliableMaxMessageSize(config.Mtu);
  218. default:
  219. return KcpPeer.ReliableMaxMessageSize(config.Mtu, ReceiveWindowSize);
  220. }
  221. }
  222. // kcp reliable channel max packet size is MTU * WND_RCV
  223. // this allows 144kb messages. but due to head of line blocking, all
  224. // other messages would have to wait until the maxed size one is
  225. // delivered. batching 144kb messages each time would be EXTREMELY slow
  226. // and fill the send queue nearly immediately when using it over the
  227. // network.
  228. // => instead we always use MTU sized batches.
  229. // => people can still send maxed size if needed.
  230. public override int GetBatchThreshold(int channelId) =>
  231. KcpPeer.UnreliableMaxMessageSize(config.Mtu);
  232. // server statistics
  233. // LONG to avoid int overflows with connections.Sum.
  234. // see also: https://github.com/vis2k/Mirror/pull/2777
  235. public long GetAverageMaxSendRate() =>
  236. server.connections.Count > 0
  237. ? server.connections.Values.Sum(conn => conn.MaxSendRate) / server.connections.Count
  238. : 0;
  239. public long GetAverageMaxReceiveRate() =>
  240. server.connections.Count > 0
  241. ? server.connections.Values.Sum(conn => conn.MaxReceiveRate) / server.connections.Count
  242. : 0;
  243. long GetTotalSendQueue() =>
  244. server.connections.Values.Sum(conn => conn.SendQueueCount);
  245. long GetTotalReceiveQueue() =>
  246. server.connections.Values.Sum(conn => conn.ReceiveQueueCount);
  247. long GetTotalSendBuffer() =>
  248. server.connections.Values.Sum(conn => conn.SendBufferCount);
  249. long GetTotalReceiveBuffer() =>
  250. server.connections.Values.Sum(conn => conn.ReceiveBufferCount);
  251. // PrettyBytes function from DOTSNET
  252. // pretty prints bytes as KB/MB/GB/etc.
  253. // long to support > 2GB
  254. // divides by floats to return "2.5MB" etc.
  255. public static string PrettyBytes(long bytes)
  256. {
  257. // bytes
  258. if (bytes < 1024)
  259. return $"{bytes} B";
  260. // kilobytes
  261. else if (bytes < 1024L * 1024L)
  262. return $"{(bytes / 1024f):F2} KB";
  263. // megabytes
  264. else if (bytes < 1024 * 1024L * 1024L)
  265. return $"{(bytes / (1024f * 1024f)):F2} MB";
  266. // gigabytes
  267. return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB";
  268. }
  269. protected virtual void OnGUIStatistics()
  270. {
  271. GUILayout.BeginArea(new Rect(5, 110, 300, 300));
  272. if (ServerActive())
  273. {
  274. GUILayout.BeginVertical("Box");
  275. GUILayout.Label("SERVER");
  276. GUILayout.Label($" connections: {server.connections.Count}");
  277. GUILayout.Label($" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s");
  278. GUILayout.Label($" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s");
  279. GUILayout.Label($" SendQueue: {GetTotalSendQueue()}");
  280. GUILayout.Label($" ReceiveQueue: {GetTotalReceiveQueue()}");
  281. GUILayout.Label($" SendBuffer: {GetTotalSendBuffer()}");
  282. GUILayout.Label($" ReceiveBuffer: {GetTotalReceiveBuffer()}");
  283. GUILayout.EndVertical();
  284. }
  285. if (ClientConnected())
  286. {
  287. GUILayout.BeginVertical("Box");
  288. GUILayout.Label("CLIENT");
  289. GUILayout.Label($" MaxSendRate: {PrettyBytes(client.MaxSendRate)}/s");
  290. GUILayout.Label($" MaxRecvRate: {PrettyBytes(client.MaxReceiveRate)}/s");
  291. GUILayout.Label($" SendQueue: {client.SendQueueCount}");
  292. GUILayout.Label($" ReceiveQueue: {client.ReceiveQueueCount}");
  293. GUILayout.Label($" SendBuffer: {client.SendBufferCount}");
  294. GUILayout.Label($" ReceiveBuffer: {client.ReceiveBufferCount}");
  295. GUILayout.EndVertical();
  296. }
  297. GUILayout.EndArea();
  298. }
  299. // OnGUI allocates even if it does nothing. avoid in release.
  300. #if UNITY_EDITOR || DEVELOPMENT_BUILD
  301. protected virtual void OnGUI()
  302. {
  303. if (statisticsGUI) OnGUIStatistics();
  304. }
  305. #endif
  306. protected virtual void OnLogStatistics()
  307. {
  308. if (ServerActive())
  309. {
  310. string log = "kcp SERVER @ time: " + NetworkTime.localTime + "\n";
  311. log += $" connections: {server.connections.Count}\n";
  312. log += $" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s\n";
  313. log += $" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s\n";
  314. log += $" SendQueue: {GetTotalSendQueue()}\n";
  315. log += $" ReceiveQueue: {GetTotalReceiveQueue()}\n";
  316. log += $" SendBuffer: {GetTotalSendBuffer()}\n";
  317. log += $" ReceiveBuffer: {GetTotalReceiveBuffer()}\n\n";
  318. Log.Info(log);
  319. }
  320. if (ClientConnected())
  321. {
  322. string log = "kcp CLIENT @ time: " + NetworkTime.localTime + "\n";
  323. log += $" MaxSendRate: {PrettyBytes(client.MaxSendRate)}/s\n";
  324. log += $" MaxRecvRate: {PrettyBytes(client.MaxReceiveRate)}/s\n";
  325. log += $" SendQueue: {client.SendQueueCount}\n";
  326. log += $" ReceiveQueue: {client.ReceiveQueueCount}\n";
  327. log += $" SendBuffer: {client.SendBufferCount}\n";
  328. log += $" ReceiveBuffer: {client.ReceiveBufferCount}\n\n";
  329. Log.Info(log);
  330. }
  331. }
  332. public override string ToString() => $"KCP {port}";
  333. }
  334. }
  335. //#endif MIRROR <- commented out because MIRROR isn't defined on first import yet