Beetle在Tcp通讯中使用Protobuf
Protobuf是google制定的一种对象序列化格式,而在.net下的实现有protobuf-net.而protobuf-net在序列化方面有着出色的性能,效率是.net二进制序列化几倍,而序列化后所占的空间也少于.net二进制序列化;除了以上两个优势外Protobuf有着一个更大的优势就是和其他平台交互的兼容性,在现有大部分流行的语言平台中基本都有Protobuf的实现.因此采用protobuf进行对象序列化是个不错的选择.接下来详细讲解Beetle实现对protobuf-net支持.
定义协议格式
为了保证TCP数据流正确处理,首先要做的事情还是要制定一个处理协议,来保证数据处理的有效性.
数据包同样也是两部分组件,头描述消息总长度,消息体是主内容.由于Protobuf-net序列化的数据交不包括消息类型,所以在协议中必须包括类型名称用于提供给对方实始化对应的类型过行反序列化操作.
实现具体的分析器和消息适配器
协议制定后就可以进行分析器的实现,由于采用头4字节描述大小,所以分析器从HeadSizeOfPackage基础类派生下载重写相关方法即可;完整实现代码如下:
using System; using System.Collections.Generic; using System.Text; using Beetle; namespace Beetle.Packages { public class ProtobufPackage : Beetle.HeadSizeOfPackage { public ProtobufPackage() { } public ProtobufPackage(Beetle.TcpChannel channel) : base(channel) { } protected override void WriteMessageType(IMessage msg, BufferWriter writer) { } public override object WriteCast(object message) { MessageAdapter ma = new MessageAdapter(); ma.Message = message; return ma; } public override object ReadCast(object message) { return ((MessageAdapter)message).Message; } protected override IMessage ReadMessageByType(BufferReader reader, out object typeTag) { typeTag = "ProtobufAdapter"; return new MessageAdapter(); } static Dictionary<string, Type> mTypes = new Dictionary<string, Type>(256); static Dictionary<Type, string> mNames = new Dictionary<Type, string>(256); public static void LoadAssembly() { try { string path = AppDomain.CurrentDomain.DynamicDirectory; LoadAssembly(path); path = AppDomain.CurrentDomain.BaseDirectory; LoadAssembly(path); } catch { } } public static void LoadAssembly(string path) { if (!string.IsNullOrEmpty(path)) { foreach (string item in System.IO.Directory.GetFiles(path, "*.dll")) { try { LoadAssembly(System.Reflection.Assembly.LoadFile(item)); } catch { } } } } public static void LoadAssembly(System.Reflection.Assembly assembly) { foreach (Type t in assembly.GetTypes()) { ProtoBuf.ProtoContractAttribute[] pc = Smark.Core.Functions.GetTypeAttributes<ProtoBuf.ProtoContractAttribute>(t, false); if (pc.Length > 0) { string name = t.Name; if (!string.IsNullOrEmpty(pc[0].Name)) name = pc[0].Name; mTypes.Add(name, t); mNames.Add(t, name); } } } class MessageAdapter : Beetle.IMessage { public object Message { get; set; } public void Load(Beetle.BufferReader reader) { string type = reader.ReadString(); Beetle.ByteArraySegment segment = ByteArraySegment; reader.ReadByteArray(segment); using (System.IO.Stream stream = new System.IO.MemoryStream(segment.Array, 0, segment.Count)) { Message = ProtoBuf.Meta.RuntimeTypeModel.Default.Deserialize(stream, null, mTypes[type]); } } public void Save(Beetle.BufferWriter writer) { writer.Write(mNames[Message.GetType()]); Beetle.ByteArraySegment segment = ByteArraySegment; using (System.IO.Stream stream = new System.IO.MemoryStream(segment.Array)) { ProtoBuf.Meta.RuntimeTypeModel.Default.Serialize(stream, Message); segment.SetInfo(0, (int)stream.Position); } writer.Write(segment); } [ThreadStatic] private static ByteArraySegment mByteArraySegment = null; public ByteArraySegment ByteArraySegment { get { if (mByteArraySegment == null) mByteArraySegment = new ByteArraySegment(TcpUtils.DataPacketMaxLength); return mByteArraySegment; } } } } }分析主要重写了ReadCast,WriteCast,而两个Cast方法主要是把消息进行一个适配器包装到一个IMessage对象中提供给组件处理.通过适配器MessageAdapter来实现终于对象的序列化和反序列化操作,并整合的流中.为了方便处理消息对应称名称还添加了分析程序类型来加载对应的类型和名称关系映射.接下来订制一个简单的注册对象
[ProtoContract] public class Register { [ProtoMember(1)] public string UserName { get; set; } [ProtoMember(2)] public string EMail { get; set; } [ProtoMember(3)] public DateTime ResponseTime { get; set; } }
实现相应的TCP服务
协议分析器扩展完成后就通过它来实现一个基于protobuf对象处理的TCP交互服务.
class Program : Beetle.ServerBase<Beetle.Packages.ProtobufPackage> { static void Main(string[] args) { Beetle.Packages.ProtobufPackage.LoadAssembly(typeof(Program).Assembly); Beetle.TcpUtils.Setup("beetle"); Program server = new Program(); server.Open(9034); Console.WriteLine("server start @9034"); Console.Read(); } protected override void OnConnected(object sender, Beetle.ChannelEventArgs e) { base.OnConnected(sender, e); Console.WriteLine("{0} connected", e.Channel.EndPoint); } protected override void OnDisposed(object sender, Beetle.ChannelDisposedEventArgs e) { base.OnDisposed(sender, e); Console.WriteLine("{0} disposed", e.Channel.EndPoint); } protected override void OnError(object sender, Beetle.ChannelErrorEventArgs e) { base.OnError(sender, e); Console.WriteLine("{0} error {1}", e.Channel.EndPoint, e.Exception.Message); } protected override void OnMessageReceive(Beetle.PacketRecieveMessagerArgs e) { Messages.Register register = (Messages.Register)e.Message; register.ResponseTime = DateTime.Now; e.Channel.Send(register); } }TCP服务的实现和原来的实现方式一致,只是继承的ServerBase的泛型参是基于protobuf的协议分析器类型.
连接到服务进行对象通讯
同样接入服务端的代码只是改变一下泛型参类型即可
private void cmdConnect_Click(object sender, EventArgs e) { try { channel = Beetle.TcpServer.CreateClient<Beetle.Packages.ProtobufPackage>(txtIPAddress.Text, 9034, OnReceive); channel.ChannelDisposed += OnDisposed; channel.ChannelError += OnError; channel.BeginReceive(); cmdRegister.Enabled = true; cmdConnect.Enabled = false; } catch (Exception e_) { MessageBox.Show(e_.Message); } } private void OnReceive(Beetle.PacketRecieveMessagerArgs e) { Register reg = (Register)e.Message; Invoke(new Action<Register>(r => { txtREMail.Text = r.EMail; txtRName.Text = r.UserName; txtResponseTime.Text = r.ResponseTime.ToString(); }), reg); } private void OnDisposed(object sender, Beetle.ChannelEventArgs e) { Invoke(new Action<Beetle.ChannelEventArgs>(s => { txtStatus.Text = "disconnect!"; cmdRegister.Enabled = false; cmdConnect.Enabled = true; }), e); } private void OnError(object sender, Beetle.ChannelErrorEventArgs e) { Invoke(new Action<Beetle.ChannelErrorEventArgs>(r => { txtStatus.Text = r.Exception.Message; }), e); }把对象发送给服务端
Register register = new Register(); register.EMail = txtEMail.Text; register.UserName = txtName.Text; channel.Send(register);
运行效果
下载代码:Code
总结
由于Protobuf制定的协议是开放的,所以很多平台下都有相关实现包括:c++,java,php等.通过整合protobuf作为协议载体可以方便地和其他平台进行TCP数据交互整合.