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

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);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351