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的時候順便把之前客戶端的看了些:沒眼看。費盡心思總算把前端的網絡給撿出來了。正常說來網絡這部分無論先後端都會有單獨的線程來處理,然而這裏的客戶端不是這樣,貼代碼吧git

  

//部分代碼
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結構,再扔給邏輯層。
整個數據流向就理通了,這是主程幹得出來的?github

還有更奇葩的 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 在拆包的時候將邏輯層完整的包扔進一個隊裏裏面,基本上只須要拷貝一次,只有在兩種極端狀況纔會多一次拷貝:網絡

    一、尾部在頭部後面,且容量比減去尾部小於 MIN_READ_BUF,當前接收到的總數據不足 MIN_READ_BUF 框架

    二、頭部在尾部後面,頭部減去尾部小於 MIN_READ_BUF,就是當前的包比較大,基本是是最大的包大於 Buffer大小。異步

    對包大於 Buffer 狀況,要麼邏輯層實現分頁(像Skynet最大的包不能超過64K),要麼加大 Buffer 空間,讀 Buffer 會出現頭在尾部後面自動擴漲的話會出現屢次拷貝得不償失,還有前端不多會發生一個超大的數據包,寫 Buffer 能夠自動擴漲,有時候包比較大,好比:獲取揹包信息

相關文章
相關標籤/搜索