美文网首页
Unity C#版本KCP 使用与代码详述(无需针对各个平台Bu

Unity C#版本KCP 使用与代码详述(无需针对各个平台Bu

作者: unlockc | 来源:发表于2019-03-27 10:07 被阅读0次

    KCP牺牲流量,换取“低延时” 来解决TCP“相对”高延时弊端

    • 去Github https://github.com/limpo1989/kcp-csharp 拷贝C#版本KCP底层文件: kcp.cs和switch_queue.cs到Unity工程
    • 封装一个UDP来应用KCP:udp_socket_v1.cs(此文件Github 也有,只不过是我加大部分注释,让大家容易了解)。KCP是一个算法、理念,并不具有UDP功能,所以才需要封装一个UDP。
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    
    //等更新Unity2008就可以使用 thread save、least gc的C#版笨KCP https://github.com/KumoKyaku/KCP
    namespace KcpProject.v1
    {
    
        // 模拟TCP客户端主动发送握手数据
        // 服务器下发conv
        public class UdpSocket
        {
    
            private static readonly DateTime utc_time = new DateTime(1970, 1, 1);
    
            public static UInt32 iclock()
            {
                return (UInt32)(Convert.ToInt64(DateTime.UtcNow.Subtract(utc_time).TotalMilliseconds) & 0xffffffff);
            }
    
            public enum cliEvent
            {
                Connected = 0,
                ConnectFailed = 1,
                Disconnect = 2,
                RcvMsg = 3,
            }
    
            private const UInt32 CONNECT_TIMEOUT = 5000;
            private const UInt32 RESEND_CONNECT = 500;
    
            private UdpClient mUdpClient;
            private IPEndPoint mIPEndPoint;
            private IPEndPoint mSvrEndPoint;
            private Action<cliEvent, byte[], string> evHandler;
            private KCP mKcp;
            private bool mNeedUpdateFlag;
            private UInt32 mNextUpdateTime;
    
            /// <summary> true:正在Socket连接过程, false:Socket连接完成、或者没开始 </summary>
            private bool mInConnectStage;
    
            private bool mConnectSucceed;
    
            /// <summary>Socket开始连接的时间点:用于判断Socket连接是否超时</summary>
            private UInt32 mConnectStartTime;
    
            private UInt32 mLastSendConnectTime;
    
            private SwitchQueue<byte[]> mRecvQueue = new SwitchQueue<byte[]>(128);
    
            public UdpSocket(Action<cliEvent, byte[], string> handler)
            {
                evHandler = handler;
            }
    
            public void Connect(string host, UInt16 port)
            {
                mSvrEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
                mUdpClient = new UdpClient(host, port);
    
                //连接成功是没有回调的,靠ReceiveCallback回调收到第一条消息,来作为连接成功回调
                mUdpClient.Connect(mSvrEndPoint);
    
                reset_state();
    
                mInConnectStage = true;
                mConnectStartTime = iclock();
    
                mUdpClient.BeginReceive(ReceiveCallback, this);
            }
    
            void ReceiveCallback(IAsyncResult ar)
            {
                Byte[] data = (mIPEndPoint == null) ?
                    mUdpClient.Receive(ref mIPEndPoint) : //第一次收到消息调用这里
                    mUdpClient.EndReceive(ar, ref mIPEndPoint);//第二次开始收到消息调用这里:由mUdpClient.BeginReceive(ReceiveCallback, this);触发
    
                if (null != data)
                    OnData(data);
    
                if (mUdpClient != null)
                {
                    // try to receive again.
                    mUdpClient.BeginReceive(ReceiveCallback, this);
                }
            }
    
            /// <summary>
            /// 网络层收到消息时,调用它
            /// </summary>
            void OnData(byte[] buf)
            {
                mRecvQueue.Push(buf);
            }
    
            void reset_state()
            {
                mNeedUpdateFlag = false;
                mNextUpdateTime = 0;
    
                mInConnectStage = false;
                mConnectSucceed = false;
                mConnectStartTime = 0;
                mLastSendConnectTime = 0;
                mRecvQueue.Clear();
                mKcp = null;
            }
    
            string dump_bytes(byte[] buf, int size)
            {
                var sb = new StringBuilder(size * 2);
                for (var i = 0; i < size; i++)
                {
                    sb.Append(buf[i]);
                    sb.Append(" ");
                }
                return sb.ToString();
            }
    
            void init_kcp(UInt32 conv)
            {
                mKcp = new KCP(conv, (byte[] buf, int size) =>
                {
                    //mKcp.Send(buf)时,不马上Send,时机到时,kcp才Send。kcp Send时会回调这个函数
                    mUdpClient.Send(buf, size);
                });
    
                mKcp.NoDelay(1, 10, 2, 1);
            }
    
            public void Send(byte[] buf)
            {
                mKcp.Send(buf);//不马上Send,时机到时,kcp才Send
                mNeedUpdateFlag = true;//强制kcp提前update
            }
    
            public void Send(string str)
            {
                Send(System.Text.ASCIIEncoding.ASCII.GetBytes(str));
            }
    
            public void Update()
            {
                update(iclock());
            }
    
            public void Close()
            {
                mUdpClient.Close();
                evHandler(cliEvent.Disconnect, null, "Closed");
            }
    
            void process_connect_packet()
            {
                mRecvQueue.Switch();
    
                //socket连接成功(握手成功)
                if (!mRecvQueue.Empty())
                {
                    var buf = mRecvQueue.Pop();
    
                    //前端后端必须一致的udp标志
                    UInt32 conv = 0;
    
                    KCP.ikcp_decode32u(buf, 0, ref conv);//decode 32 bits unsigned int (lsb) -> (byte[] p, int offset, ref UInt32 c)
    
                    if (conv <= 0)
                        throw new Exception("inlvaid connect back packet");
    
                    init_kcp(conv);
    
                    mInConnectStage = false;
                    mConnectSucceed = true;
    
                    evHandler(cliEvent.Connected, null, null);
                }
            }
    
            void process_recv_queue()
            {
                mRecvQueue.Switch();
    
                //收到一个正常的数据包
                while (!mRecvQueue.Empty())
                {
                    var buf = mRecvQueue.Pop();
    
                    mKcp.Input(buf);
                    mNeedUpdateFlag = true;//强制kcp提前update
                    for (var size = mKcp.PeekSize(); size > 0; size = mKcp.PeekSize())
                    {
                        var buffer = new byte[size];
                        if (mKcp.Recv(buffer) > 0)
                        {
                            evHandler(cliEvent.RcvMsg, buffer, null);
                        }
                    }
                }
            }
    
            bool connect_timeout(UInt32 current)
            {
                return current - mConnectStartTime > CONNECT_TIMEOUT;
            }
    
            bool need_send_connect_packet(UInt32 current)
            {
                return current - mLastSendConnectTime > RESEND_CONNECT;
            }
    
            void update(UInt32 current)
            {
                if (mInConnectStage)
                {
                    if (connect_timeout(current))
                    {
                        evHandler(cliEvent.ConnectFailed, null, "Timeout");
                        mInConnectStage = false;
                        return;
                    }
    
                    //这里要发一个4字节的包给服务器 来 握手(ReceiveCallback一段时间没有收到,会继续发)
                    if (need_send_connect_packet(current))
                    {
                        mLastSendConnectTime = current;
                        mUdpClient.Send(new byte[4] { 0, 0, 0, 0 }, 4);
                    }
    
                    process_connect_packet();
    
                    return;
                }
    
                if (mConnectSucceed)
                {
                    process_recv_queue();
    
                    if (mNeedUpdateFlag || current >= mNextUpdateTime)
                    {
                        //给kcp注入生命,让它自己决定什么时候发送、重发 数据包
                        mKcp.Update(current);
                        mNextUpdateTime = mKcp.Check(current);
                        mNeedUpdateFlag = false;
                    }
                }
            }
        }
    
    }
    
    • 测试 上面应用KCP的UDP
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    //你自己写一个脚本类来测试
    class Program
    {
        static void test_v1(string host, UInt16 port)
        {
            var wait_response = true;
    
            KcpProject.v1.UdpSocket client = null;
            // 创建一个实例
            client = new KcpProject.v1.UdpSocket((KcpProject.v1.UdpSocket.cliEvent ev, byte[] buf, string err) =>
            {
                wait_response = false;
                //事件回调
                switch (ev)
                { 
                    case KcpProject.v1.UdpSocket.cliEvent.Connected:
                        Console.WriteLine("connected.");
                        client.Send("Hello KCP.");
                        break;
                    case KcpProject.v1.UdpSocket.cliEvent.ConnectFailed:
                        Console.WriteLine("connect failed. {0}", err);
                        break;
                    case KcpProject.v1.UdpSocket.cliEvent.Disconnect:
                        Console.WriteLine("disconnect. {0}", err);
                        break;
                    case KcpProject.v1.UdpSocket.cliEvent.RcvMsg:
                        Console.WriteLine("recv message: {0}", System.Text.ASCIIEncoding.ASCII.GetString(buf) );
                        break;
                }
            });
    
            client.Connect(host, port);
    
            while (wait_response)
            {
                client.Update();
                System.Threading.Thread.Sleep(10);
            }
        }
    
        static void test_v2(string host, UInt16 port)
        {
            var wait_response = true;
    
            KcpProject.v2.UdpSocket client = null;
    
            // 创建一个实例
            client = new KcpProject.v2.UdpSocket((byte[] buf) => 
            {
                wait_response = false;
                Console.WriteLine("recv message: {0}", System.Text.ASCIIEncoding.ASCII.GetString(buf));
            });
    
            // 绑定端口
            client.Connect(host, port);
    
            // 发送消息
            client.Send("Hello KCP.");
    
            // update.
            while (wait_response)
            {
                client.Update();//你自己写一个脚本类,放到Update函数里测试
                System.Threading.Thread.Sleep(10);
            }
        }
    
        static void Main(string[] args)
        {
           // 测试v1版本,有握手过程,服务器决定conv的分配
           test_v1("192.168.1.2", 4444);
        }
    }
    

    相关文章

      网友评论

          本文标题:Unity C#版本KCP 使用与代码详述(无需针对各个平台Bu

          本文链接:https://www.haomeiwen.com/subject/soeavqtx.html