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 可以自动扩涨,有时候包比较大,比如:获取背包信息

  • 相关阅读:
    mysql索引
    springboot mybatis 后台框架平台 shiro 权限 集成代码生成器
    java 企业网站源码模版 有前后台 springmvc SSM 生成静态化
    java springMVC SSM 操作日志 4级别联动 文件管理 头像编辑 shiro redis
    activiti工作流的web流程设计器整合视频教程 SSM和独立部署
    .Net Core中的ObjectPool
    文件操作、流相关类梳理
    .Net Core中的配置文件源码解析
    .Net Core中依赖注入服务使用总结
    消息中间件RabbitMQ(一)
  • 原文地址:https://www.cnblogs.com/beat/p/9320935.html
Copyright © 2011-2022 走看看