diff --git a/Assets/Mirror/Transports/KCP/kcp2k/VERSION.txt b/Assets/Mirror/Transports/KCP/kcp2k/VERSION.txt index 4c145463d..2c308799e 100755 --- a/Assets/Mirror/Transports/KCP/kcp2k/VERSION.txt +++ b/Assets/Mirror/Transports/KCP/kcp2k/VERSION.txt @@ -1,3 +1,7 @@ +V1.24 [2022-12-14] +- KcpClient: fixed NullReferenceException when connection without a server. + added test coverage to ensure this never happens again. + V1.23 [2022-12-07] - KcpClient: rawReceiveBuffer exposed - fix: KcpServer RawSend uses connection.remoteEndPoint instead of the helper diff --git a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/Common.cs b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/Common.cs index 77acab3bc..8123f293e 100644 --- a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/Common.cs +++ b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/Common.cs @@ -16,7 +16,7 @@ public static bool ResolveHostname(string hostname, out IPAddress[] addresses) } catch (SocketException exception) { - Log.Info($"[KCP] Failed to resolve host: {hostname} reason: {exception}"); + Log.Info($"Failed to resolve host: {hostname} reason: {exception}"); addresses = null; return false; } @@ -34,7 +34,7 @@ public static void MaximizeSocketBuffers(Socket socket) socket.SetReceiveBufferToOSLimit(); socket.SetSendBufferToOSLimit(); - Log.Info($"[KCP] RecvBuf = {initialReceive}=>{socket.ReceiveBufferSize} ({socket.ReceiveBufferSize/initialReceive}x) SendBuf = {initialSend}=>{socket.SendBufferSize} ({socket.SendBufferSize/initialSend}x) maximized to OS limits!"); + Log.Info($"Kcp: RecvBuf = {initialReceive}=>{socket.ReceiveBufferSize} ({socket.ReceiveBufferSize/initialReceive}x) SendBuf = {initialSend}=>{socket.SendBufferSize} ({socket.SendBufferSize/initialSend}x) maximized to OS limits!"); } } } diff --git a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpClient.cs b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpClient.cs index 20d2b9d15..6c62011a3 100644 --- a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpClient.cs +++ b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpClient.cs @@ -24,21 +24,20 @@ public class KcpClient // to reuse the buffer. protected readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF]; - // events + // callbacks + // even for errors, to allow liraries to show popups etc. + // instead of logging directly. + // (string instead of Exception for ease of use and to avoid user panic) public Action OnConnected; public Action, KcpChannel> OnData; public Action OnDisconnected; - // error callback instead of logging. - // allows libraries to show popups etc. - // (string instead of Exception for ease of use and to avoid user panic) public Action OnError; // state public bool connected; public KcpClient(Action OnConnected, - Action, - KcpChannel> OnData, + Action, KcpChannel> OnData, Action OnDisconnected, Action OnError) { @@ -62,7 +61,7 @@ public void Connect(string address, { if (connected) { - Log.Warning("[KCP] client already connected!"); + Log.Warning("KCP: client already connected!"); return; } @@ -72,7 +71,7 @@ public void Connect(string address, // setup events peer.OnAuthenticated = () => { - Log.Info($"[KCP] OnClientConnected"); + Log.Info($"KCP: OnClientConnected"); connected = true; OnConnected(); }; @@ -83,7 +82,7 @@ public void Connect(string address, }; peer.OnDisconnected = () => { - Log.Info($"[KCP] OnClientDisconnected"); + Log.Info($"KCP: OnClientDisconnected"); connected = false; peer = null; socket?.Close(); @@ -96,13 +95,13 @@ public void Connect(string address, OnError(error, reason); }; - Log.Info($"[KCP] Client connecting to {address}:{port}"); + Log.Info($"KcpClient: connect to {address}:{port}"); // try resolve host name if (!Common.ResolveHostname(address, out IPAddress[] addresses)) { // pass error to user callback. no need to log it manually. - peer.OnError(ErrorCode.DnsResolve, $"[KCP] Failed to resolve host: {address}"); + peer.OnError(ErrorCode.DnsResolve, $"Failed to resolve host: {address}"); peer.OnDisconnected(); return; } @@ -119,7 +118,7 @@ public void Connect(string address, Common.MaximizeSocketBuffers(socket); } // otherwise still log the defaults for info. - else Log.Info($"[KCP] Client RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(maximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit."); + else Log.Info($"KcpClient: RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(maximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit."); // bind to endpoint so we can use send/recv instead of sendto/recvfrom. socket.Connect(remoteEndPoint); @@ -156,7 +155,7 @@ protected virtual void RawReceive() // the other end closing the connection is not an 'error'. // but connections should never just end silently. // at least log a message for easier debugging. - Log.Info($"[KCP] ClientConnection: looks like the other end has closed the connection. This is fine: {ex}"); + Log.Info($"KCP ClientConnection: looks like the other end has closed the connection. This is fine: {ex}"); peer.Disconnect(); } } @@ -172,7 +171,7 @@ public void Send(ArraySegment segment, KcpChannel channel) { if (!connected) { - Log.Warning("[KCP] Can't send because client not connected!"); + Log.Warning("KCP: can't send because client not connected!"); return; } @@ -198,11 +197,10 @@ public void TickIncoming() // recv on socket first, then process incoming // (even if we didn't receive anything. need to tick ping etc.) // (connection is null if not active) - if (peer != null) - { - RawReceive(); - peer.TickIncoming(); - } + if (peer != null) RawReceive(); + + // RawReceive may have disconnected peer. null check again. + peer?.TickIncoming(); } // process outgoing messages. should be called after updating the world. diff --git a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpHeader.cs b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpHeader.cs index f79c80273..781779613 100644 --- a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpHeader.cs +++ b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpHeader.cs @@ -8,7 +8,7 @@ public enum KcpHeader : byte { // don't react on 0x00. might help to filter out random noise. Handshake = 1, - // ping goes over reliable & KcpHeader for now. could go over reliable + // ping goes over reliable & KcpHeader for now. could go over unreliable // too. there is no real difference except that this is easier because // we already have a KcpHeader for reliable messages. // ping is only used to keep it alive, so latency doesn't matter. diff --git a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpPeer.cs b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpPeer.cs index 6f9d5de87..e3ac34e2c 100644 --- a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpPeer.cs +++ b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpPeer.cs @@ -39,7 +39,7 @@ public class KcpPeer // internal time. // StopWatch offers ElapsedMilliSeconds and should be more precise than // Unity's time.deltaTime over long periods. - readonly Stopwatch refTime = new Stopwatch(); + readonly Stopwatch watch = new Stopwatch(); // we need to subtract the channel byte from every MaxMessageSize // calculation. @@ -116,9 +116,9 @@ public static int ReliableMaxMessageSize(uint rcv_wnd) => internal const int QueueDisconnectThreshold = 10000; // getters for queue and buffer counts, used for debug info - public int SendQueueCount => kcp.snd_queue.Count; - public int ReceiveQueueCount => kcp.rcv_queue.Count; - public int SendBufferCount => kcp.snd_buf.Count; + public int SendQueueCount => kcp.snd_queue.Count; + public int ReceiveQueueCount => kcp.rcv_queue.Count; + public int SendBufferCount => kcp.snd_buf.Count; public int ReceiveBufferCount => kcp.rcv_buf.Count; // maximum send rate per second can be calculated from kcp parameters @@ -176,7 +176,7 @@ public KcpPeer( this.timeout = timeout; - refTime.Start(); + watch.Start(); } void HandleTimeout(uint time) @@ -223,7 +223,7 @@ void HandleChoked() // see QueueSizeDisconnect comments. // => include all of kcp's buffers and the unreliable queue! int total = kcp.rcv_queue.Count + kcp.snd_queue.Count + - kcp.rcv_buf.Count + kcp.snd_buf.Count; + kcp.rcv_buf.Count + kcp.snd_buf.Count; if (total >= QueueDisconnectThreshold) { // pass error to user callback. no need to log it manually. @@ -248,45 +248,41 @@ void HandleChoked() // -> to avoid buffering, unreliable messages call OnData directly. bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) { - int msgSize = kcp.PeekSize(); - if (msgSize > 0) - { - // only allow receiving up to buffer sized messages. - // otherwise we would get BlockCopy ArgumentException anyway. - if (msgSize <= kcpMessageBuffer.Length) - { - // receive from kcp - int received = kcp.Receive(kcpMessageBuffer, msgSize); - if (received >= 0) - { - // extract header & content without header - header = (KcpHeader)kcpMessageBuffer[0]; - message = new ArraySegment(kcpMessageBuffer, 1, msgSize - 1); - lastReceiveTime = (uint)refTime.ElapsedMilliseconds; - return true; - } - else - { - // if receive failed, close everything - // pass error to user callback. no need to log it manually. - // GetType() shows Server/ClientConn instead of just Connection. - OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed with error={received}. closing connection."); - Disconnect(); - } - } - // we don't allow sending messages > Max, so this must be an - // attacker. let's disconnect to avoid allocation attacks etc. - else - { - // pass error to user callback. no need to log it manually. - OnError(ErrorCode.InvalidReceive, $"{GetType()}: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); - Disconnect(); - } - } - message = default; header = KcpHeader.Disconnect; - return false; + + int msgSize = kcp.PeekSize(); + if (msgSize <= 0) return false; + + // only allow receiving up to buffer sized messages. + // otherwise we would get BlockCopy ArgumentException anyway. + if (msgSize > kcpMessageBuffer.Length) + { + // we don't allow sending messages > Max, so this must be an + // attacker. let's disconnect to avoid allocation attacks etc. + // pass error to user callback. no need to log it manually. + OnError(ErrorCode.InvalidReceive, $"{GetType()}: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); + Disconnect(); + return false; + } + + // receive from kcp + int received = kcp.Receive(kcpMessageBuffer, msgSize); + if (received < 0) + { + // if receive failed, close everything + // pass error to user callback. no need to log it manually. + // GetType() shows Server/ClientConn instead of just Connection. + OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed with error={received}. closing connection."); + Disconnect(); + return false; + } + + // extract header & content without header + header = (KcpHeader)kcpMessageBuffer[0]; + message = new ArraySegment(kcpMessageBuffer, 1, msgSize - 1); + lastReceiveTime = (uint)watch.ElapsedMilliseconds; + return true; } void TickIncoming_Connected(uint time) @@ -308,7 +304,7 @@ void TickIncoming_Connected(uint time) // we were waiting for a handshake. // it proves that the other end speaks our protocol. // GetType() shows Server/ClientConn instead of just Connection. - Log.Info($"[KCP] {GetType()}: received handshake"); + Log.Info($"{GetType()}: received handshake"); state = KcpState.Authenticated; OnAuthenticated?.Invoke(); break; @@ -350,7 +346,7 @@ void TickIncoming_Authenticated(uint time) { // should never receive another handshake after auth // GetType() shows Server/ClientConn instead of just Connection. - Log.Warning($"[KCP] {GetType()}: received invalid header {header} while Authenticated. Disconnecting the connection."); + Log.Warning($"{GetType()}: received invalid header {header} while Authenticated. Disconnecting the connection."); Disconnect(); break; } @@ -381,7 +377,7 @@ void TickIncoming_Authenticated(uint time) { // disconnect might happen // GetType() shows Server/ClientConn instead of just Connection. - Log.Info($"[KCP] {GetType()}: received disconnect message"); + Log.Info($"{GetType()}: received disconnect message"); Disconnect(); break; } @@ -391,7 +387,7 @@ void TickIncoming_Authenticated(uint time) public void TickIncoming() { - uint time = (uint)refTime.ElapsedMilliseconds; + uint time = (uint)watch.ElapsedMilliseconds; try { @@ -443,7 +439,7 @@ public void TickIncoming() public void TickOutgoing() { - uint time = (uint)refTime.ElapsedMilliseconds; + uint time = (uint)watch.ElapsedMilliseconds; try { @@ -495,78 +491,78 @@ public void TickOutgoing() // feed the rest to kcp. public void RawInput(byte[] buffer, int offset, int size) { - // parse channel - if (size > 0) - { - byte channel = buffer[offset + 0]; - switch (channel) - { - case (byte)KcpChannel.Reliable: - { - // input into kcp, but skip channel byte - int input = kcp.Input(buffer, offset + 1, size - 1); - if (input != 0) - { - // GetType() shows Server/ClientConn instead of just Connection. - Log.Warning($"[KCP] {GetType()}: Input failed with error={input} for buffer with length={size - 1}"); - } - break; - } - case (byte)KcpChannel.Unreliable: - { - // ideally we would queue all unreliable messages and - // then process them in ReceiveNext() together with the - // reliable messages, but: - // -> queues/allocations/pools are slow and complex. - // -> DOTSNET 10k is actually slower if we use pooled - // unreliable messages for transform messages. - // - // DOTSNET 10k benchmark: - // reliable-only: 170 FPS - // unreliable queued: 130-150 FPS - // unreliable direct: 183 FPS(!) - // - // DOTSNET 50k benchmark: - // reliable-only: FAILS (queues keep growing) - // unreliable direct: 18-22 FPS(!) - // - // -> all unreliable messages are DATA messages anyway. - // -> let's skip the magic and call OnData directly if - // the current state allows it. - if (state == KcpState.Authenticated) - { - ArraySegment message = new ArraySegment(buffer, offset + 1, size - 1); - OnData?.Invoke(message, KcpChannel.Unreliable); + // ensure valid size: at least 1 byte for channel + if (size <= 0) return; - // set last receive time to avoid timeout. - // -> we do this in ANY case even if not enabled. - // a message is a message. - // -> we set last receive time for both reliable and - // unreliable messages. both count. - // otherwise a connection might time out even - // though unreliable were received, but no - // reliable was received. - lastReceiveTime = (uint)refTime.ElapsedMilliseconds; - } - else - { - // should never happen - // pass error to user callback. no need to log it manually. - // GetType() shows Server/ClientConn instead of just Connection. - OnError(ErrorCode.InvalidReceive, $"{GetType()}: received unreliable message in state {state}. Disconnecting the connection."); - Disconnect(); - } - break; - } - default: + // parse channel + byte channel = buffer[offset + 0]; + switch (channel) + { + case (byte)KcpChannel.Reliable: + { + // input into kcp, but skip channel byte + int input = kcp.Input(buffer, offset + 1, size - 1); + if (input != 0) { - // not a valid channel. random data or attacks. - // pass error to user callback. no need to log it manually. - // GetType() shows Server/ClientConn instead of just Connection. - OnError(ErrorCode.InvalidReceive, $"{GetType()}: Disconnecting connection because of invalid channel header: {channel}"); - Disconnect(); - break; + // GetType() shows Server/ClientConn instead of just Connection. + Log.Warning($"{GetType()}: Input failed with error={input} for buffer with length={size - 1}"); } + break; + } + case (byte)KcpChannel.Unreliable: + { + // ideally we would queue all unreliable messages and + // then process them in ReceiveNext() together with the + // reliable messages, but: + // -> queues/allocations/pools are slow and complex. + // -> DOTSNET 10k is actually slower if we use pooled + // unreliable messages for transform messages. + // + // DOTSNET 10k benchmark: + // reliable-only: 170 FPS + // unreliable queued: 130-150 FPS + // unreliable direct: 183 FPS(!) + // + // DOTSNET 50k benchmark: + // reliable-only: FAILS (queues keep growing) + // unreliable direct: 18-22 FPS(!) + // + // -> all unreliable messages are DATA messages anyway. + // -> let's skip the magic and call OnData directly if + // the current state allows it. + if (state == KcpState.Authenticated) + { + ArraySegment message = new ArraySegment(buffer, offset + 1, size - 1); + OnData?.Invoke(message, KcpChannel.Unreliable); + + // set last receive time to avoid timeout. + // -> we do this in ANY case even if not enabled. + // a message is a message. + // -> we set last receive time for both reliable and + // unreliable messages. both count. + // otherwise a connection might time out even + // though unreliable were received, but no + // reliable was received. + lastReceiveTime = (uint)watch.ElapsedMilliseconds; + } + else + { + // should never happen + // pass error to user callback. no need to log it manually. + // GetType() shows Server/ClientConn instead of just Connection. + OnError(ErrorCode.InvalidReceive, $"{GetType()}: received unreliable message in state {state}. Disconnecting the connection."); + Disconnect(); + } + break; + } + default: + { + // not a valid channel. random data or attacks. + // pass error to user callback. no need to log it manually. + // GetType() shows Server/ClientConn instead of just Connection. + OnError(ErrorCode.InvalidReceive, $"{GetType()}: Disconnecting connection because of invalid channel header: {channel}"); + Disconnect(); + break; } } } @@ -586,42 +582,46 @@ void RawSendReliable(byte[] data, int length) void SendReliable(KcpHeader header, ArraySegment content) { // 1 byte header + content needs to fit into send buffer - if (1 + content.Count <= kcpSendBuffer.Length) // TODO + if (1 + content.Count > kcpSendBuffer.Length) // TODO { - // copy header, content (if any) into send buffer - kcpSendBuffer[0] = (byte)header; - if (content.Count > 0) - Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count); - - // send to kcp for processing - int sent = kcp.Send(kcpSendBuffer, 0, 1 + content.Count); - if (sent < 0) - { - // GetType() shows Server/ClientConn instead of just Connection. - OnError(ErrorCode.InvalidSend, $"{GetType()}: Send failed with error={sent} for content with length={content.Count}"); - } + // otherwise content is larger than MaxMessageSize. let user know! + // GetType() shows Server/ClientConn instead of just Connection. + OnError(ErrorCode.InvalidSend, $"{GetType()}: Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize(kcp.rcv_wnd)}"); + return; + } + + // copy header, content (if any) into send buffer + kcpSendBuffer[0] = (byte)header; + if (content.Count > 0) + Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count); + + // send to kcp for processing + int sent = kcp.Send(kcpSendBuffer, 0, 1 + content.Count); + if (sent < 0) + { + // GetType() shows Server/ClientConn instead of just Connection. + OnError(ErrorCode.InvalidSend, $"{GetType()}: Send failed with error={sent} for content with length={content.Count}"); } - // otherwise content is larger than MaxMessageSize. let user know! - // GetType() shows Server/ClientConn instead of just Connection. - else OnError(ErrorCode.InvalidSend, $"{GetType()}: Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize(kcp.rcv_wnd)}"); } void SendUnreliable(ArraySegment message) { // message size needs to be <= unreliable max size - if (message.Count <= UnreliableMaxMessageSize) + if (message.Count > UnreliableMaxMessageSize) { - // copy channel header, data into raw send buffer, then send - rawSendBuffer[0] = (byte)KcpChannel.Unreliable; - Buffer.BlockCopy(message.Array, message.Offset, rawSendBuffer, 1, message.Count); - - // IO send - ArraySegment segment = new ArraySegment(rawSendBuffer, 0, message.Count + 1); - RawSend(segment); + // otherwise content is larger than MaxMessageSize. let user know! + // GetType() shows Server/ClientConn instead of just Connection. + Log.Error($"{GetType()}: Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={UnreliableMaxMessageSize}"); + return; } - // otherwise content is larger than MaxMessageSize. let user know! - // GetType() shows Server/ClientConn instead of just Connection. - else Log.Error($"[KCP] {GetType()}: Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={UnreliableMaxMessageSize}"); + + // copy channel header, data into raw send buffer, then send + rawSendBuffer[0] = (byte)KcpChannel.Unreliable; + Buffer.BlockCopy(message.Array, message.Offset, rawSendBuffer, 1, message.Count); + + // IO send + ArraySegment segment = new ArraySegment(rawSendBuffer, 0, message.Count + 1); + RawSend(segment); } // server & client need to send handshake at different times, so we need @@ -632,8 +632,8 @@ void SendUnreliable(ArraySegment message) // => handshake info needs to be delivered, so it goes over reliable. public void SendHandshake() { - // GetType() shows Server/ClientConn instead of just Connection. - Log.Info($"[KCP] {GetType()}: sending Handshake to other end!"); + // GetType() shows Server/ClientConn instead of just Connection. + Log.Info($"{GetType()}: sending Handshake to other end!"); SendReliable(KcpHeader.Handshake, default); } @@ -647,7 +647,7 @@ public void SendData(ArraySegment data, KcpChannel channel) { // pass error to user callback. no need to log it manually. // GetType() shows Server/ClientConn instead of just Connection. - OnError(ErrorCode.InvalidSend, $"[KCP] {GetType()}: tried sending empty message. This should never happen. Disconnecting."); + OnError(ErrorCode.InvalidSend, $"{GetType()}: tried sending empty message. This should never happen. Disconnecting."); Disconnect(); return; } @@ -700,7 +700,7 @@ public void Disconnect() // set as Disconnected, call event // GetType() shows Server/ClientConn instead of just Connection. - Log.Info($"[KCP] {GetType()}: Disconnected."); + Log.Info($"{GetType()}: Disconnected."); state = KcpState.Disconnected; OnDisconnected?.Invoke(); } diff --git a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpServer.cs index 71156cec9..71703db4c 100644 --- a/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpServer.cs +++ b/Assets/Mirror/Transports/KCP/kcp2k/highlevel/KcpServer.cs @@ -10,13 +10,13 @@ namespace kcp2k { public class KcpServer { - // events + // callbacks + // even for errors, to allow liraries to show popups etc. + // instead of logging directly. + // (string instead of Exception for ease of use and to avoid user panic) public Action OnConnected; public Action, KcpChannel> OnData; public Action OnDisconnected; - // error callback instead of logging. - // allows libraries to show popups etc. - // (string instead of Exception for ease of use and to avoid user panic) public Action OnError; // socket configuration @@ -101,7 +101,7 @@ public KcpServer(Action OnConnected, // create newClientEP either IPv4 or IPv6 newClientEP = DualMode ? new IPEndPoint(IPAddress.IPv6Any, 0) - : new IPEndPoint(IPAddress.Any, 0); + : new IPEndPoint(IPAddress.Any, 0); } public virtual bool IsActive() => socket != null; @@ -111,23 +111,21 @@ public virtual void Start(ushort port) // only start once if (socket != null) { - Log.Warning("[KCP] server already started!"); + Log.Warning("KCP: server already started!"); return; } - Log.Info($"[KCP] Starting server on port {port}"); - // listen if (DualMode) { - // IPv6 socket with DualMode + // IPv6 socket with DualMode @ "::" : port socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); socket.DualMode = true; socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port)); } else { - // IPv4 socket + // IPv4 socket @ "0.0.0.0" : port socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.Bind(new IPEndPoint(IPAddress.Any, port)); } @@ -140,7 +138,7 @@ public virtual void Start(ushort port) Common.MaximizeSocketBuffers(socket); } // otherwise still log the defaults for info. - else Log.Info($"[KCP] Server: RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(MaximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit."); + else Log.Info($"KcpServer: RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(MaximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit."); } public void Send(int connectionId, ArraySegment segment, KcpChannel channel) @@ -172,7 +170,7 @@ public IPEndPoint GetClientEndPoint(int connectionId) // io - poll. // return true if there is data to read. // after which RawReceive will be called. - // virtual because for relays, + // virtual for relays. protected virtual bool RawPoll() => socket != null && socket.Poll(0, SelectMode.SelectRead); @@ -211,7 +209,7 @@ protected virtual void RawSend(int connectionId, ArraySegment data) // get the connection's endpoint if (!connections.TryGetValue(connectionId, out KcpServerConnection connection)) { - Debug.LogWarning($"[KCP] Server.RawSend: invalid connectionId={connectionId}"); + Debug.LogWarning($"KcpServer.RawSend: invalid connectionId={connectionId}"); return; } @@ -283,7 +281,7 @@ void ProcessNext() // add to connections dict after being authenticated. connections.Add(connectionId, connection); - Log.Info($"[KCP] server added connection({connectionId})"); + Log.Info($"KCP: server added connection({connectionId})"); // setup Data + Disconnected events only AFTER the // handshake. we don't want to fire OnServerDisconnected @@ -307,7 +305,7 @@ void ProcessNext() connectionsToRemove.Add(connectionId); // call mirror event - Log.Info($"[KCP] OnServerDisconnected({connectionId})"); + Log.Info($"KCP: OnServerDisconnected({connectionId})"); OnDisconnected(connectionId); }; @@ -318,7 +316,7 @@ void ProcessNext() }; // finally, call mirror OnConnected event - Log.Info($"[KCP] OnServerConnected({connectionId})"); + Log.Info($"KCP: OnServerConnected({connectionId})"); OnConnected(connectionId); }; @@ -345,13 +343,13 @@ void ProcessNext() // the other end closing the connection is not an 'error'. // but connections should never just end silently. // at least log a message for easier debugging. - Log.Info($"[KCP] ClientConnection: looks like the other end has closed the connection. This is fine: {ex}"); + Log.Info($"KCP ClientConnection: looks like the other end has closed the connection. This is fine: {ex}"); } } // process incoming messages. should be called before updating the world. // virtual because relay may need to inject their own ping or similar. - HashSet connectionsToRemove = new HashSet(); + readonly HashSet connectionsToRemove = new HashSet(); public virtual void TickIncoming() { while (RawPoll()) diff --git a/Assets/Mirror/Transports/KCP/kcp2k/kcp/Kcp.cs b/Assets/Mirror/Transports/KCP/kcp2k/kcp/Kcp.cs index 1bec93496..569ace0b2 100755 --- a/Assets/Mirror/Transports/KCP/kcp2k/kcp/Kcp.cs +++ b/Assets/Mirror/Transports/KCP/kcp2k/kcp/Kcp.cs @@ -907,6 +907,9 @@ void FlushBuffer() // // 'current' - current timestamp in millisec. pass it to Kcp so that // Kcp doesn't have to do any stopwatch/deltaTime/etc. code + // + // time as uint, likely to minimize bandwidth. + // uint.max = 4294967295 ms = 1193 hours = 49 days public void Update(uint currentTimeMilliSeconds) { current = currentTimeMilliSeconds;