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

转载于:https://www.cnblogs.com/beat/p/9320935.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值