Silverlight Tcp通讯中使用Protobuf.net
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/ 获取