美文网首页
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