예제 #1
0
        void SendReliable(KcpHeader header, ArraySegment <byte> content)
        {
            // 1 byte header + content needs to fit into send buffer
            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)
                {
                    KCPLog.Warning($"Send failed with error={sent} for content with length={content.Count}");
                }
            }
            // otherwise content is larger than MaxMessageSize. let user know!
            else
            {
                KCPLog.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize}");
            }
        }
예제 #2
0
        public void Connect(string address, ushort port, bool noDelay, uint interval, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV)
        {
            if (connected)
            {
                KCPLog.Warning("KCP: client already connected!");
                return;
            }

            connection = new KcpClientConnection();

            // setup events
            connection.OnAuthenticated = () =>
            {
                KCPLog.Info($"KCP: OnClientConnected");
                connected = true;
                OnConnected.Invoke();
            };
            connection.OnData = (message) =>
            {
                //Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
                OnData.Invoke(message);
            };
            connection.OnDisconnected = () =>
            {
                KCPLog.Info($"KCP: OnClientDisconnected");
                connected  = false;
                connection = null;
                OnDisconnected.Invoke();
            };

            // connect
            connection.Connect(address, port, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize);
        }
예제 #3
0
 void HandleDeadLink()
 {
     // kcp has 'dead_link' detection. might as well use it.
     if (kcp.state == -1)
     {
         KCPLog.Warning("KCP Connection dead_link detected. Disconnecting.");
         Disconnect();
     }
 }
예제 #4
0
 void HandleTimeout(uint time)
 {
     // note: we are also sending a ping regularly, so timeout should
     //       only ever happen if the connection is truly gone.
     if (time >= lastReceiveTime + TIMEOUT)
     {
         KCPLog.Warning($"KCP: Connection timed out after not receiving any message for {TIMEOUT}ms. Disconnecting.");
         Disconnect();
     }
 }
예제 #5
0
 public void Send(ArraySegment <byte> segment, KcpChannel channel)
 {
     if (connected)
     {
         connection.SendData(segment, channel);
     }
     else
     {
         KCPLog.Warning("KCP: can't send because client not connected!");
     }
 }
예제 #6
0
        public void Start(ushort port)
        {
            // only start once
            if (socket != null)
            {
                KCPLog.Warning("KCP: server already started!");
            }

            // listen
#if UNITY_SWITCH
            // Switch does not support ipv6
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.Bind(new IPEndPoint(IPAddress.Any, port));
#else
            socket          = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
            socket.DualMode = true;
            socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
#endif
        }
예제 #7
0
        void TickIncoming_Connected(uint time)
        {
            // detect common events & ping
            HandleTimeout(time);
            HandleDeadLink();
            HandlePing(time);
            HandleChoked();

            // any reliable kcp message received?
            if (ReceiveNextReliable(out KcpHeader header, out ArraySegment <byte> message))
            {
                // message type FSM. no default so we never miss a case.
                switch (header)
                {
                case KcpHeader.Handshake:
                {
                    // we were waiting for a handshake.
                    // it proves that the other end speaks our protocol.
                    KCPLog.Info("KCP: received handshake");
                    state = KcpState.Authenticated;
                    OnAuthenticated?.Invoke();
                    break;
                }

                case KcpHeader.Ping:
                {
                    // ping keeps kcp from timing out. do nothing.
                    break;
                }

                case KcpHeader.Data:
                case KcpHeader.Disconnect:
                {
                    // everything else is not allowed during handshake!
                    KCPLog.Warning($"KCP: received invalid header {header} while Connected. Disconnecting the connection.");
                    Disconnect();
                    break;
                }
                }
            }
        }
예제 #8
0
        // reads the next reliable message type & content from kcp.
        // -> to avoid buffering, unreliable messages call OnData directly.
        bool ReceiveNextReliable(out KcpHeader header, out ArraySegment <byte> message)
        {
            header  = KcpHeader.Handshake;
            message = null;
            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 <byte>(kcpMessageBuffer, 1, msgSize - 1);
                        lastReceiveTime = (uint)refTime.ElapsedMilliseconds;
                        return(true);
                    }
                    else
                    {
                        // if receive failed, close everything
                        KCPLog.Warning($"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
                {
                    KCPLog.Warning($"KCP: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection.");
                    Disconnect();
                }
            }

            header = KcpHeader.Disconnect;
            return(false);
        }
예제 #9
0
        public void SendData(ArraySegment <byte> data, KcpChannel channel)
        {
            // sending empty segments is not allowed.
            // nobody should ever try to send empty data.
            // it means that something went wrong, e.g. in Mirror/DOTSNET.
            // let's make it obvious so it's easy to debug.
            if (data.Count == 0)
            {
                KCPLog.Warning("KcpConnection: tried sending empty message. This should never happen. Disconnecting.");
                Disconnect();
                return;
            }

            switch (channel)
            {
            case KcpChannel.Reliable:
                SendReliable(KcpHeader.Data, data);
                break;

            case KcpChannel.Unreliable:
                SendUnreliable(data);
                break;
            }
        }
예제 #10
0
        void HandleChoked()
        {
            // disconnect connections that can't process the load.
            // 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;

            if (total >= QueueDisconnectThreshold)
            {
                KCPLog.Warning($"KCP: disconnecting connection because it can't process data fast enough.\n" +
                               $"Queue total {total}>{QueueDisconnectThreshold}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" +
                               $"* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n" +
                               $"* Or perhaps the network is simply too slow on our end, or on the other end.\n");

                // let's clear all pending sends before disconnting with 'Bye'.
                // otherwise a single Flush in Disconnect() won't be enough to
                // flush thousands of messages to finally deliver 'Bye'.
                // this is just faster and more robust.
                kcp.snd_queue.Clear();

                Disconnect();
            }
        }
예제 #11
0
        public void RawInput(byte[] buffer, int msgLength)
        {
            // parse channel
            if (msgLength > 0)
            {
                byte channel = buffer[0];
                switch (channel)
                {
                case (byte)KcpChannel.Reliable:
                {
                    // input into kcp, but skip channel byte
                    int input = kcp.Input(buffer, 1, msgLength - 1);
                    if (input != 0)
                    {
                        KCPLog.Warning($"Input failed with error={input} for buffer with length={msgLength - 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)
                    {
                        // only process messages while not paused for Mirror
                        // scene switching etc.
                        // -> if an unreliable message comes in while
                        //    paused, simply drop it. it's unreliable!
                        if (!paused)
                        {
                            ArraySegment <byte> message = new ArraySegment <byte>(buffer, 1, msgLength - 1);
                            OnData?.Invoke(message);
                        }

                        // 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
                        KCPLog.Warning($"KCP: received unreliable message in state {state}. Disconnecting the connection.");
                        Disconnect();
                    }
                    break;
                }

                default:
                {
                    // not a valid channel. random data or attacks.
                    KCPLog.Info($"Disconnecting connection because of invalid channel header: {channel}");
                    Disconnect();
                    break;
                }
                }
            }
        }
예제 #12
0
        void TickIncoming_Authenticated(uint time)
        {
            // detect common events & ping
            HandleTimeout(time);
            HandleDeadLink();
            HandlePing(time);
            HandleChoked();

            // process all received messages
            //
            // Mirror scene changing requires transports to immediately stop
            // processing any more messages after a scene message was
            // received. and since we are in a while loop here, we need this
            // extra check.
            //
            // note while that this is mainly for Mirror, but might be
            // useful in other applications too.
            //
            // note that we check it BEFORE ever calling ReceiveNext. otherwise
            // we would silently eat the received message and never process it.
            while (!paused &&
                   ReceiveNextReliable(out KcpHeader header, out ArraySegment <byte> message))
            {
                // message type FSM. no default so we never miss a case.
                switch (header)
                {
                case KcpHeader.Handshake:
                {
                    // should never receive another handshake after auth
                    KCPLog.Warning($"KCP: received invalid header {header} while Authenticated. Disconnecting the connection.");
                    Disconnect();
                    break;
                }

                case KcpHeader.Data:
                {
                    // call OnData IF the message contained actual data
                    if (message.Count > 0)
                    {
                        //Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}");
                        OnData?.Invoke(message);
                    }
                    // empty data = attacker, or something went wrong
                    else
                    {
                        KCPLog.Warning("KCP: received empty Data message while Authenticated. Disconnecting the connection.");
                        Disconnect();
                    }
                    break;
                }

                case KcpHeader.Ping:
                {
                    // ping keeps kcp from timing out. do nothing.
                    break;
                }

                case KcpHeader.Disconnect:
                {
                    // disconnect might happen
                    KCPLog.Info("KCP: received disconnect message");
                    Disconnect();
                    break;
                }
                }
            }
        }