Protobuf.net是Protobuf協議在.net平臺下的實現,它支持源生的.net程序,Silverlight和window phon7的二進制序列化功能.在這裏主要講述在Silverlight Tcp通訊中使用Protobuf.net.在實現通訊前行制定一個通訊協議,主要是描述Protobuf.net序列化後的傳輸格式和獲取時如何反序列化.
協議描述主要分爲3部分首先是描述消息的總長度,然後跟着就是消息的類型名稱,組件通過應該名稱來創建對應的消息對象,最後面跟着的就是相關對象的Protobuf序列化數據。
協議確定後就可以進行代碼實現,由於之前已經寫了一個Silverlight Tcp組件Beetle.SL(項目地址:http://beetlesl.codeplex.com/);所以直接在它基礎上擴展就OK了。需要做的工作有兩項:實現一個消息適配器和一個協議分析器。在實現之前當然是要引用ProtoBuf.net這個組件可以到http://code.google.com/p/protobuf-net/獲取。
消息適配器實現如下
1 public class MessageAdapter:Beetle.IMessage 2 { 3 public object Message 4 { 5 get; 6 set; 7 } 8 static Dictionary<string, Type> mTypes = new Dictionary<string, Type>(256); 9 static Dictionary<Type, string> mNames = new Dictionary<Type, string>(256); 10 public static void LoadMessage(System.Reflection.Assembly assembly) 11 { 12 foreach (Type t in assembly.GetTypes()) 13 { 14 ProtoBuf.ProtoContractAttribute[] pc = (ProtoBuf.ProtoContractAttribute[])t.GetCustomAttributes(typeof(ProtoBuf.ProtoContractAttribute), false); 15 if (pc.Length > 0) 16 { 17 string name = t.Name; 18 NameAttribute[] na = (NameAttribute[])t.GetCustomAttributes(typeof(NameAttribute), false); 19 if (pc.Length > 0) 20 if(na.Length>0) 21 { 22 name = na[0].Name; 23 } 24 mTypes.Add(name, t); 25 mNames.Add(t, name); 26 } 27 } 28 } 29 public static void Send(Beetle.TcpChannel channel, object message) 30 { 31 MessageAdapter ma = new MessageAdapter(); 32 ma.Message = message; 33 channel.Send(ma); 34 } 35 public void Load(Beetle.BufferReader reader) 36 { 37 string type = reader.ReadString(); 38 byte[] data = reader.ReadByteArray(); 39 using (System.IO.Stream stream = new System.IO.MemoryStream(data,0,data.Length)) 40 { 41 Message = ProtoBuf.Meta.RuntimeTypeModel.Default.Deserialize(stream, null, mTypes[type]); 42 } 43 44 } 45 public void Save(Beetle.BufferWriter writer) 46 { 47 writer.Write(mNames[Message.GetType()]); 48 byte[] data; 49 using (System.IO.Stream stream = new System.IO.MemoryStream()) 50 { 51 ProtoBuf.Meta.RuntimeTypeModel.Default.Serialize(stream, Message); 52 53 data = new byte[stream.Length]; 54 stream.Position = 0; 55 stream.Read(data, 0, data.Length); 56 57 } 58 59 writer.Write(data); 60 61 62 } 63 }
整個過程實現是比較簡單的就是根據協議制定的內容來把序列化信息寫入BufferWriter,在獲取消息的時候也是同樣的方式從BufferReader中獲取。
分包器的實現
Beetle.SL已經提供基於頭描述大小的分包器,所以實現就更加簡單了
public class ProtoBufHeadSizePackage:Beetle.HeadSizeOfPackage { public ProtoBufHeadSizePackage() { } protected override void WriteMessageType(IMessage msg, BufferWriter writer) { } protected override IMessage ReadMessageByType(BufferReader reader, out object typeTag) { typeTag = "MessageAdapter"; return new MessageAdapter(); } }
爲了簡單定義連接也繼承TcpChannel實現一個ProtobufChannel
public class ProtoBufChannel:TcpChannel { public ProtoBufChannel() : base(new ProtoBufHeadSizePackage()) { } }
以下工作完成後,就可以在Silverlight Tcp通訊使用ProtoBuf.net來處理對象了,以下是制定一些簡單的信息。
[ProtoContract] public class Search { [ProtoMember(1)] public string CompanyName { get; set; } [ProtoMember(2)] public string City { get; set; } [ProtoMember(3)] public string Region { get; set; } } [ProtoContract] public class SearchResult { [ProtoMember(1)] public List<Customer> Customers { get; set; } } [ProtoContract] public class GetCustomerOrders { [ProtoMember(1)] public string CustomerID { get; set; } } [ProtoContract] public class CustomerOrders { [ProtoMember(1)] public IList<Order> Orders { get; set; } }
消息制定後就可以定義連接了
private ProtoBufChannel mChannel = new ProtoBufChannel(); private void UserControl_Loaded(object sender, RoutedEventArgs e) { MessageAdapter.LoadMessage(this.GetType().Assembly); mChannel.Connected += OnConnected; mChannel.Disposed += OnDisposed; mChannel.Receive += OnReceive; mChannel.Error += OnError; }
連接創建後綁定相關事件,主要事件有4個分別是連接成功,連接釋放,連接處理錯誤和數據接收事件。當這些事件定義後只需要調用一個簡單Connect方法即可連接到指定IP端口的TCP服務.
private void cmdConnect_Click(object sender, RoutedEventArgs e) { mChannel.Connect(txtIP.Text,4520); }
連接後可以直接使用Channel發送Protobuf.net可序列化的對象.
private void cmdSearch_Click(object sender, RoutedEventArgs e) { Packages.Search search = new Packages.Search(); search.CompanyName = txtCompany.Text; MessageAdapter.Send(mChannel, search); }
可以在接收數據事件根據不同的消息類型來進行一個輸出處理.
private void OnReceive(object sender, EventChannelReceiveArgs e) { MessageAdapter adapter = (MessageAdapter)e.Message; if (adapter.Message is Packages.SearchResult) { Packages.SearchResult searchresult = (Packages.SearchResult)adapter.Message; this.Dispatcher.BeginInvoke(() => { dgCustomers.ItemsSource = searchresult.Customers; }); } else if (adapter.Message is Packages.CustomerOrders) { Packages.CustomerOrders orders = (Packages.CustomerOrders)adapter.Message; this.Dispatcher.BeginInvoke(() => { dbOrders.ItemsSource = orders.Orders; }); } }
以上是一個簡單的數據查詢事例
具體代碼可以到http://beetlesl.codeplex.com/ 獲取