zoukankan      html  css  js  c++  java
  • Unity 网络通信以及buffer优化

        最近学习Unity想实现网络通信,为了对以后项目做打算,想对网络通信方面做些准备以及验证。对于mmorpg类游戏这种网络要求不是很强可以使用Tcp,但是对于Moba、FPS使用TCP有点勉为其难。以前使用 KCP + UDP 验证了 UDP 双端数据的完整性且效率比TCP要高的多,但是自己还没有没有使用 C# 实现,目前先把前端 TCP 弄好,过些日子时间空余再集成 TCP、KCP + UDP,TCP登录验证,分配UDP客户端登录识别KEY,以及要不要在Github上面开源整套RPC框架,整套RPC框架只要用过它就会觉得超爽,比GRPC等方便多了,有集成Lua,这套框架是某游戏公司的并不是我撸出来的,但是已经被我重写了大部分功能,如果开源会不会设计知识产权问题...这些都是后话了。但是我觉得每个开发人员都参与到开源事业,则中国的技术会有整体的提高,谁没有用过开源库?有一点得知道:并不是那些不开源的源码对公司有多大的商业价值,而是这些开源后因为代码实在是太烂了而导致用户不敢使用。谁面试的时候不是被问的技术有多深、多牛逼,但是你会发现公司内部的源码就是小学生写的。

        入正题吧:回想在上家的时候网络通信基本没有问题,有一点就是客户端比较卡,这段时间学Unity的时候顺便把以前客户端的看了些:没眼看。费尽心思总算把前端的网络给捡出来了。正常说来网络这部分不管前后端都会有单独的线程来处理,然而这里的客户端不是这样,贴代码吧

      

    //部分代码
    class GameLoader : MonoBehaviour
    {
        private void FixedUpdate()
        {
            IConnection main = _net.getMainConnection();
            main.onBagTimer();
        }
    }
    private void ReceiveSorket()
    {
         ...
        byte[] bytes = new byte[4096];
        int len = socket.Receive(bytes, 4096, SocketFlags.None);
         ....
    }
    virtual public void onBagTimer()
    {
        ReceiveSorket();
        byte[] ba;
        for (int i = 0; i < bagMax; i++)
        {
             if (bagArray.Count == 0)
            {
                break;
             }
              ba = bagArray[0] as byte[];
              bagArray.RemoveAt(0);
              handler(ba);
         }
    } 
    
    

    FixedUpdate 固定帧会被执行的,那就是说onBagTimer固定帧数被执行
    ReceiveSorket 中将Buff数据按照协议将数据拆解,再组装成逻辑层需要用到的二进制流,最终在 handler 回调里面将数据解析成protobuf结构,再扔给逻辑层。
    整个数据流向就理通了,这是主程干得出来的?

    还有更奇葩的 buffer 处理



    private void ReceiveSorket()
    {
           try
        {
                //Receive方法中会一直等待服务端回发消息
            //如果没有回发会一直在这里等着。
            if ((socket.Connected == false || socket.Available <= 0))
            {
                    //   Thread.Sleep(133);
                return;
            }
            //接受数据保存至bytes当中
            byte[] bytes = new byte[4096];
            int len = socket.Receive(bytes, 4096, SocketFlags.None);
            if (len <= 0)
            {
                    socket.Close();
                return;
            }
            byte[] new_bytes = new byte[len];
            Array.Copy(bytes, 0, new_bytes, 0, len);
            buffer.pushByteArray(new_bytes);
            List<byte[]> temp = buffer.split();
            if (temp == null)
            {
                    return;
            }
            bagArray.AddRange(temp);
        }
    }
    public void pushByteArray(byte[] ba)
    {
        if (buffer == null)
        {
            readLength(ba, 0);
            buffer = ba;
        }
        else
        {
            byte[] temp = new byte[buffer.Length + ba.Length];
            buffer.CopyTo(temp,0);
            ba.CopyTo(temp,buffer.Length);
            buffer = temp;
            readLength(buffer, afterLength);
        }
    }
    public List<byte[]> split()
    {
        try
        {
            //判断当前缓存包长度是否够读取
            if (buffer == null || length == 0 || (buffer != null && (buffer.Length - afterLength) < length))
            {
                return null;
            }
            bag = new List<byte[]>();  //截取数据包
            while (true)
            {
                tempBag = new byte[length];
                Array.Copy(buffer,afterLength,tempBag,0,length);
                afterLength += length;
                length = 0;
                bag.Add(tempBag);
                if (!readLength(buffer, afterLength) || buffer.Length - afterLength == 0)
                { //检查是否还有下一组消息数据
                    if (buffer.Length - afterLength == 0)
                    { //当前缓存区如果木有数据则清空
                        buffer = null;
                        afterLength = 0;
                    }
                    break;
                }
            }
        }
        catch (Exception ex)
        { 
        }
        return bag;
    }

       

    每次最多接收4096个字节到临时 bytes 中,在new一个实际接收长度的 new_bytes 将 bytes 拷贝到 new_bytes 中, pushByteArray 中将新 buffer 和 上一次接收的数据一起再来一次数据拷贝(缓存起来),  split 又一次拷贝(将缓存数据按照包长拆解程逻辑层用的数据包),这 Buffer 拷贝次数太多了吧,谁家游戏网络卡顿的时候不是在怼后端?

        重点来了:对前端 Buffer 处理优化(单独的网络线程 + 循环数组)

         

    buffer 基类
        public class BufferLoop
        {
            protected const int CHUNK_SIZE = 1024 * 2;
            protected byte[] _buff;
            protected int _head = 0;
            protected int _tail = 0;
            protected int _capacity = 0;
    
            public BufferLoop(int bufsize)
            {
                int c = (bufsize + CHUNK_SIZE - 1) / CHUNK_SIZE;
                _capacity = c * CHUNK_SIZE;
                _buff = new byte[_capacity];
                _head = 0;
                _tail = 0;
    
            }
    
            public int Capacity()
            {
                return _capacity;
            }
    
            public int Size()
            {
                if (_head < _tail)
                    return _tail - _head;
                else if (_head > _tail)
                    return _capacity - _head + _tail;
                return 0;
            }
    
            public void OffsetHead(int off)
            {
                _head = (_head + off) % _capacity;
            }
    
            public void OffsetTail(int off)
            {
                _tail = (_tail + off) % _capacity;
            }
    
            public byte[] GetBuffer()
            {
                return _buff;
            }
            public int GetHead()
            {
                return _head;
            }
            public int GetTail()
            {
                return _tail;
            }
    
            public int GetMaxBufferSize() { return System.Convert.ToInt32(CHUNK_SIZE * 0.9); }
        }
    Buffer_loop_r.cs 读 buffer
        public class Buffer_loop_r : BufferLoop
        {
            const int MIN_READ_BUF = 10;
            public Buffer_loop_r(int bufsize) : base(bufsize)
            {
            }
    
            public int Read(ref byte[] buf, int len, bool offset = true)
            {
                if (len <= 0) return 0;
                else if (len > Size()) return 0;
    
                if (_head < _tail)
                {
                    Array.Copy(_buff, _head, buf, 0, len);
                }
                else
                {
                    int rLen = _capacity - _head;
                    if (len <= rLen)
                    {
                        Array.Copy(_buff, _head, buf, 0, len);
                    }
                    else
                    {
                        Array.Copy(_buff, _head, buf, 0, rLen);
                        Array.Copy(_buff, 0, buf, rLen, len - rLen);
                    }
                }
    
                if (offset)
                    OffsetHead(len);
                return len;
            }
            //可用来接收的空间 如果不足 MIN_READ_BUF 则将 数据
            public int GetSpaceR()
            {
                if (GetSpaceRead() <= MIN_READ_BUF)
                    ReplaceR();
                return GetSpaceRead();
            }
            //
            protected int GetSpaceRead()
            {
                if (_head <= _tail)
                    return _capacity - _tail;
                else
                    return _head - _tail;
            }
            protected void ReplaceR()
            {
                if (_head <= _tail)
                {
                    int s = Size();
                    if (s > 0)
                    {
                        //不需要处理局部重叠
                        Array.Copy(_buff, _head, _buff, 0, s);
                    }
                    _head = 0;
                    _tail = s % _capacity;
                }
            }
        }
    Buffer_loop_w.cs 写 Buffer 
    
        public class Buffer_loop_w : BufferLoop
        {
            public Buffer_loop_w(int bufsize) : base(bufsize)
            {
                
            }
    
            public int Write(byte[] buf, int len)
            {
                if (_head <= _tail)
                {
                    int rLen = _capacity - _tail;
                    if (len <= rLen)
                    {
                        Array.Copy(buf, 0, _buff, _tail, len);
                    }
                    else
                    {
                        Array.Copy(buf, 0, _buff, _tail, rLen);
                        Array.Copy(buf, rLen, _buff, 0, len - rLen);
                    }
                }
                else
                {
                    Array.Copy(buf, 0, _buff, _tail, len);
                }
                
                OffsetTail(len);
                return len;
            }
    
            public int GetSizeS()
            {
                if (_head <= _tail)
                {
                    return _tail - _head;
                }
                else if (_head > _tail)
                {
                    return _capacity - _head;
                }
                return 0;
            }
    
            public int GetSpaceW()
            {
                return Capacity() - Size();
            }
    
            public void Replace(ref Buffer_loop_w buffW)
            {
                int h = buffW._head;
                int t = buffW._tail;
                if (h < t)
                {
                    int len = t - h;
                    Array.Copy(buffW._buff, h, _buff, 0, len);
                    _head = 0;
                    _tail = len;
                }
                else if (h > t)
                {
                    int len = buffW._capacity - h;
                    Array.Copy(buffW._buff, h, _buff, 0, len);
    
                    if (t > 0)
                        Array.Copy(buffW._buff, 0, _buff, len, t);
                    _head = 0;
                    _tail = len + t;
                }
            }
        }

    使用方式

    var bytesRead = _client.Receive(_inBuffer.GetBuffer(), _inBuffer.GetTail(), len, SocketFlags.None);
    _inBuffer.OffsetTail(bytesRead);
    这里我用的是同步,也有使用BeginReceive实现的,但是网络线程没有别的事,使用异步的的话那大部分时间在sleep
    var bytesSent = _client.Send(_outBuffer.GetBuffer(), _outBuffer.GetHead(), len, SocketFlags.None);
     _outBuffer.OffsetHead(bytesSent);

        对接收 Buffer 在拆包的时候将逻辑层完整的包扔进一个队里里面,基本上只需要拷贝一次,只有在两种极端情况才会多一次拷贝:

        1、尾部在头部后面,且容量比减去尾部小于 MIN_READ_BUF,当前接收到的总数据不足 MIN_READ_BUF 

        2、头部在尾部后面,头部减去尾部小于 MIN_READ_BUF,就是当前的包比较大,基本是是最大的包大于 Buffer大小。

        对包大于 Buffer 情况,要么逻辑层实现分页(像Skynet最大的包不能超过64K),要么加大 Buffer 空间,读 Buffer 会出现头在尾部后面自动扩涨的话会出现多次拷贝得不偿失,还有前端很少会发生一个超大的数据包,写 Buffer 可以自动扩涨,有时候包比较大,比如:获取背包信息

  • 相关阅读:
    341. Flatten Nested List Iterator
    667. Beautiful Arrangement II
    953. Verifying an Alien Dictionary
    1704. Determine if String Halves Are Alike
    MySQL数据库的数据类型详解【转】
    Dart之数组(List)的相关方法总结【转】
    Flutter解决点击非输入框时关闭键盘问题及TextFormField焦点切换问题【转】
    Flutter Switch开关【转】
    Image Picker
    Flutter按钮添加背景图片及文字【转】
  • 原文地址:https://www.cnblogs.com/beat/p/9320935.html
Copyright © 2011-2022 走看看