《炉石传说》建筑设计欣赏(7):采用Google.ProtocolBuffers处理网络消息
這一次,琢磨了一下Unity3D網(wǎng)絡(luò)游戲發(fā)展的網(wǎng)絡(luò)信息處理。服務(wù)器的網(wǎng)絡(luò)游戲一般都是自主研發(fā),因此,相應(yīng)的網(wǎng)絡(luò)消息處理應(yīng)該培養(yǎng)自己。client/現(xiàn)在使用的郵件服務(wù)器之間的價差JSON和Google.ProtocolBuffers有兩種常見的方法。平爐碼看其處理。代碼寫的還是非常好的,把它的思路分析一下。與大家分享。
總體機制描寫敘述
我們想要達到的目標大概是這種:- 有N個網(wǎng)絡(luò)消息,每一個消息相應(yīng)一個Proto中的message描寫敘述;
- 每一個消息相應(yīng)一個數(shù)字ID;
- 底層在收到消息是,將其解析成為Google.ProtocolBuffers.IMessage對象,這個對象的詳細類型應(yīng)該是前面那個message生成的代碼;
- 發(fā)送消息就簡單了,由于知道其類型,能夠直接運行序列化。
爐石使用Google.ProtocolBuffers類庫,能夠看這里:http://www.nuget.org/packages/Google.ProtocolBuffers/
消息發(fā)送
發(fā)送的機制非常easy,首先使用ProtocolBuffer生成的message類構(gòu)造一個消息對象,比如:ConnectAPI.SendPing() public static void SendPing() {Ping.Builder body = Ping.CreateBuilder();QueueGamePacket(0x73, body);s_lastGameServerPacketSentTime = DateTime.Now; } 底層會構(gòu)造一個“PegasusPacket”數(shù)據(jù)包對象。加入到發(fā)送隊列之中,這個數(shù)據(jù)包對象主要包括3部分:消息ID。消息大小,詳細消息數(shù)據(jù)。詳見PegasusPacket.Encode()函數(shù): 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是流式的。所以底層應(yīng)該檢測數(shù)據(jù)包頭,并收集到一個完整的數(shù)據(jù)包,然后再發(fā)送到上層解析。這部分邏輯是在”ClientConnection<PacketType>.BytesReceived()“中實現(xiàn)的。當(dāng)收到完整數(shù)據(jù)包時。會在主線程中觸發(fā)”O(jiān)nPacketCompleted“事件,實際上會調(diào)用到”ConnectAPI.PacketReceived()“,其內(nèi)部主要是調(diào)用了”ConnectAPI.QueuePacketReceived()“,這個函數(shù)負責(zé)將TCP層接收到的byte[]解析成相應(yīng)的IMessage對象。
重點來了!因為網(wǎng)絡(luò)層發(fā)過來的數(shù)據(jù)包,僅僅包括一個消息ID。那么client就須要解決從ID找到相應(yīng)的消息Type的問題。
想象中無非有兩種方式去做:1是手動記錄每一個ID相應(yīng)的Type;2是搞一個中間的相應(yīng)關(guān)系的類,附加上自己定義的Attribute,然后在使用反射機制自己主動收集這些類,事實上和前者也差點兒相同。
爐石採用了第一種方式。總體機制是這種:
- client每一個消息相應(yīng)一個PacketDecoder的派生類對象;
- ConnectAPI類使用一個字典,用來保存<消息ID,Decoder對象>之間的相應(yīng)關(guān)系:ConnectAPI.s_packetDecoders:SortedDictionary<Int32,ConnectAPI.PacketDecoder>;
- 假設(shè)每一個消息都要寫一個Decoder,而其內(nèi)部代碼由全然一致,豈不是非常蛋疼?!
好吧,我們用模板來實現(xiàn),詳見興許分析;
- 在ConnectAPI.ConnectInit()初始化的時候。創(chuàng)建Decoder對象。并保存到上述dict之中,類似這樣:
?s_packetDecoders.Add(0x74, new DefaultProtobufPacketDecoder<Pong, Pong.Builder>()); - 最后在上述的收到完整數(shù)據(jù)包的函數(shù)中,依據(jù)數(shù)據(jù)包中記錄的消息ID。去查找Decoder。然后調(diào)用其方法得到詳細的消息對象。類似這樣:
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);}
OK,爐石是使用使用ProtocolBuffers來處理網(wǎng)絡(luò)消息的機制就是這樣,是不是已經(jīng)非常清晰啦!
版權(quán)聲明:本文博主原創(chuàng)文章,博客,未經(jīng)同意不得轉(zhuǎn)載。
轉(zhuǎn)載于:https://www.cnblogs.com/blfshiye/p/4876797.html
總結(jié)
以上是生活随笔為你收集整理的《炉石传说》建筑设计欣赏(7):采用Google.ProtocolBuffers处理网络消息的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2016030206 - mysql常用
- 下一篇: JavaScript密码复杂度