《爐石傳說》架構設計賞析(7):使用Google.ProtocolBuffers處理網路訊息,

來源:互聯網
上載者:User

《爐石傳說》架構設計賞析(7):使用Google.ProtocolBuffers處理網路訊息,

這段時間琢磨了一下Unity3D網路遊戲開發中的網路訊息處理。網路遊戲的服務端一般都是自主開發的,所以對應的網路訊息處理也要自己開發。用戶端/服務端之間的訊息傳到目前使用JSON和Google.ProtocolBuffers是兩種常見的做法。開啟爐石的代碼看了看它的處理方式,感覺代碼寫的還是很好的,把它的思路分析一下,與大家分享。

整體機制描述我們想要達到的目標大概是這樣的:
  • 有N個網路訊息,每個訊息對應一個Proto中的message描述;
  • 每個訊息對應一個數字ID;
  • 底層在收到訊息是,將其解析成為Google.ProtocolBuffers.IMessage對象,這個對象的具體類型應該是前面那個message產生的程式碼;
  • 發送訊息就簡單了,因為知道其類型,可以直接執行序列化;

爐石使用Google.ProtocolBuffers類庫,可以看這裡:http://www.nuget.org/packages/Google.ProtocolBuffers/

訊息發送發送的機制很簡單,首先使用ProtocolBuffer產生的message類構造一個訊息對象,例如:ConnectAPI.SendPing()
public static void SendPing(){    Ping.Builder body = Ping.CreateBuilder();    QueueGamePacket(0x73, body);    s_lastGameServerPacketSentTime = DateTime.Now;}
底層會構造一個“PegasusPacket”資料包對象,添加到發送隊列之中,這個資料包對象主要包含3部分:訊息ID,訊息大小,具體訊息資料。詳見PegasusPacket.Encode()函數:
public override byte[] Encode(){    if (!(this.Body is IMessageLite))    {        return null;    }    IMessageLite body = (IMessageLite) this.Body;    this.Size = body.SerializedSize;    byte[] destinationArray = new byte[8 + this.Size];    Array.Copy(BitConverter.GetBytes(this.Type), 0, destinationArray, 0, 4);    Array.Copy(BitConverter.GetBytes(this.Size), 0, destinationArray, 4, 4);    body.WriteTo(CodedOutputStream.CreateInstance(destinationArray, 8, this.Size));    return destinationArray;}
訊息接收與解析接下來我們重點看一下訊息的接收與解析機制。首先因為TCP是流式的,所以底層應該檢測資料包頭,並收集到一個完整的資料包,然後再發送到上層解析,這部分邏輯是在”ClientConnection<PacketType>.BytesReceived()“中實現的。當收到完整資料包時,會在主線程中觸發”OnPacketCompleted“事件,實際上會調用到”ConnectAPI.PacketReceived()“,其內部主要是調用了”ConnectAPI.QueuePacketReceived()“,這個函數負責將TCP層接收到的byte[]解析成對應的IMessage對象。
重點來了!由於網路層發過來的資料包,只包含一個訊息ID,那麼用戶端就需要解決從ID找到相應的訊息Type的問題。想象中無非有兩種方式去做:1是手動記錄每個ID對應的Type;2是搞一個中間的對應關係的類,附加上自訂的Attribute,然後在使用反射機制自動收集這些類,其實和前者也差不多。爐石採用了第一種方式。整體機制是這樣的:
  • 用戶端每個訊息對應一個PacketDecoder的衍生類別對象;
  • ConnectAPI類使用一個字典,用來儲存<訊息ID,Decoder對象>之間的對應關係:ConnectAPI.s_packetDecoders:SortedDictionary<Int32,ConnectAPI.PacketDecoder>;
  • 如果每個訊息都要寫一個Decoder,而其內部代碼由完全一致,豈不是很蛋疼?!好吧,我們用模板來實現,詳見後續分析;
  • 在ConnectAPI.ConnectInit()初始化的時候,建立Decoder對象,並儲存到上述dict之中,類似這樣:
     s_packetDecoders.Add(0x74, new DefaultProtobufPacketDecoder<Pong, Pong.Builder>());
  • 最後在上述的收到完整資料包的函數中,根據資料包中記錄的訊息ID,去尋找Decoder,然後調用其方法得到具體的訊息對象,類似這樣:
         if (s_packetDecoders.TryGetValue(packet.Type, out decoder))    {        PegasusPacket item = decoder.HandlePacket(packet);        if (item != null)        {            queue.Enqueue(item);        }    }    else    {        Debug.LogError("Could not find a packet decoder for a packet of type " + packet.Type);    }
    最後我們看一下,Decoder模板的實現技巧。首先訊息解析的具體操作是有Google.ProtocolBuffers產生的程式碼去實現的,所以具體操作流程是完全一致的,這些寫到基類的的靜態模板函數中:
    public abstract class PacketDecoder{    // Methods    public abstract PegasusPacket HandlePacket(PegasusPacket p);    public static PegasusPacket HandleProtoBuf<TMessage, TBuilder>(PegasusPacket p) where TMessage: IMessageLite<TMessage, TBuilder> where TBuilder: IBuilderLite<TMessage, TBuilder>, new()    {        byte[] body = (byte[]) p.Body;        TBuilder local2 = default(TBuilder);        TBuilder local = (local2 == null) ? Activator.CreateInstance<TBuilder>() : default(TBuilder);        p.Body = local.MergeFrom(body).Build();        return p;    }}
    其次,使用一個模板衍生類別,實現HandlePacket()這個虛函數,主要的目的只是把TMessage和TBuilder這兩個類型傳給那個靜態函數而已:
    public class DefaultProtobufPacketDecoder<TMessage, TBuilder> : ConnectAPI.PacketDecoder where TMessage: IMessageLite<TMessage, TBuilder> where TBuilder: IBuilderLite<TMessage, TBuilder>, new(){    // Methods    public override PegasusPacket HandlePacket(PegasusPacket p)    {        return ConnectAPI.PacketDecoder.HandleProtoBuf<TMessage, TBuilder>(p);    }}

    OK,爐石是使用使用ProtocolBuffers來處理網路訊息的機制就是這樣,是不是已經很清晰啦!



    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.