Protobuf的設計非常適用於在網路通訊中的資料載體,它序列化出來的資料量少再加上以K-V的方式來儲存資料,對訊息的版本相容性非常強;還有一個比較大的優點就是有著很多的語言平臺支援。下面講解一下如何在TCP通訊應用中整合Protobuf.
Protobuf提供一種簡單的物件訊息方式來描述資料的儲存,通過相關實現可以簡單地實現資料流和物件之間的轉換。但由於Protobuf序列化後的資訊並不包括一些相應物件型別描述,只有訊息體的內容;因此在進行通訊互動過程中預設情況是不知道這些資料和物件的對應關係;既然對方不清楚這些資料對應那種型別,那資料還源成物件就根本沒辦法入手。所以把Protobuf用到網路通訊中我們還需要外加一些協議來規範資料的對應關係。
在通訊協議上只需要在訊息體中先寫入型別然後再寫入Protobuf資料,TypeName主要是用於對方配匹資料對應的物件關係,這個TypeName是string還是int或其他就根據自己喜好來制定了。
在通訊上問題就解決了,但還要面對實際中使用的一種情況訊息太多了……那處理TypeName->Object則一個蛋痛的事情。還好在.net和android下有類元素表這玩意還能在執行行進行操作,從而可以大大節省這些if判斷處理。由於本人熟悉的語言平臺有限,在這裡分別提供java和c#的解決方法。
- Java
public static void Register(Class<?> protobufclass) { try { ProtoMessageHandler mb; Class<?>[] protoObjs = protobufclass.getClasses(); for (Class<?> item : protoObjs) { if(item==null) continue; if (!item.isInterface() && item.getSuperclass().equals( com.google.protobuf.GeneratedMessage.class)) { mb = new ProtoMessageHandler(); mb.SetType(item); mMessageTbl.put(item.getSimpleName(), mb); } } } catch (Exception e) { } }
由於使用Protoc工具生成的類都會生成一個大類裡,所以只需要註冊指定的類然後對所有繼承com.google.protobuf.GeneratedMessage的內嵌類找出來並記錄在一個Map中即可。
String name= stream.ReadUTF(); ProtoMessageHandler handler = ProtoPackage.GetMsgHandler(name); if(handler==null) throw new Exception(name+" message type notfound!"); Message=(com.google.protobuf.GeneratedMessage) handler.GetObject(stream.GetStream());
以上是通過型別值獲取對應的物件,並填充Protobuf資料。
- c#
相對於android平臺下的實現,.net的實現就更簡單些了。
public static void Register(System.Reflection.Assembly assembly) { foreach(Type type in assembly.GetTypes()) { if (!type.IsAbstract && !type.IsInterface && type.GetCustomAttributes(typeof(ProtoBuf.ProtoContractAttribute), false).Length > 0) mProtoTbl[type.Name] = type; } }
string name = reader.ReadUTF(); Type type = ProtoPakcage.GetProtoType(name); Message = ProtoBuf.Meta.RuntimeTypeModel.Default.Deserialize(reader.Stream, null, type);
通過以上方法在使用Protobuf的時候就不用擔心訊息太多寫if寫到手軟了,也不容易出錯。不過有一點要注意的就是類的名稱一定要對應,否則就無法匹配到訊息了。如果想得到完全整的程式碼可以到https://beetlenp.codeplex.com獲取。