Beetle在TCP通讯中使用二进制序列化对象传输

筅森魡賤 提交于 2020-02-26 02:10:00

组件发送的对象都必须实现IMessage接口,通过实现接口来描述一个对象的序列化过程;自定义读写流的好处就是可以得到更好的性能,但缺点也相对明显的就是工作量比较多.在很多应用场合下所追求的并不是性能第一,而是希望省下这些烦锁的工作,为了应付这种需要组件可以实现一个简单的消息适配器来完成这样的功能.下面通过扩展的方式来实现组件对.net二进制序列化对象转输的支持.

定义协议格式

在TCP下进行数据交互通讯首先是要制定一个通讯的应用协议,由于组件提供基于结束符和头大小描述的基础分析器,可以从这基础分析协中派生下来.似下通过一个图来描述协议的具体细节

协议描述比较简单消息头是4个字节用于存储消息的总长度,而序列化存储的数据则是总长度减去头4位.

实现具体的分析器和消息适配器

协议制定后就可以进行分析器的实现,由于采用头4字节描述大小,所以分析器从HeadSizeOfPackage基础类派生下载重写相关方法即可;完整实现代码如下:

    public class BinaryFormatterPackage:HeadSizeOfPackage
    {
        public BinaryFormatterPackage()
        {
        }
        
        public BinaryFormatterPackage(TcpChannel channel)
            : base(channel)
        {
        }
     

        public override object ReadCast(object message)
        {
            return ((MessageAdapter)message).Message;
        }

        public override object WriteCast(object message)
        {
            MessageAdapter adapter = new MessageAdapter();
            adapter.Message = message;
            return adapter;
        }

        public override IMessage MessageRead(BufferReader reader)
        {
            IMessage msg = null;
            object typeTag;
            msg = ReadMessageByType(reader, out typeTag);
            if (msg == null)
                throw NetTcpException.TypeNotFound(typeTag.ToString());
            try
            {
                msg.Load(reader);
            }
            catch (Exception e)
            {
                NetTcpException err = NetTcpException.ObjectLoadError(typeTag.ToString(), e);
                throw err;
            }
            return msg;
        }

        protected override void WriteMessageType(IMessage msg, BufferWriter writer)
        {
            
        }

        protected override IMessage ReadMessageByType(BufferReader reader, out object typeTag)
        {
            typeTag = "Adapter";
            return new MessageAdapter();
        }

        class MessageAdapter:IMessage
        {
            public object Message
            {
                get;
                set;
            }
            public void Load(BufferReader reader)
            {
                ByteArraySegment bas = ByteArraySegment;
                try
                {
                    int count = reader.ReadInt32();
                    reader.Read(count - 4, bas);
                 
                    using (System.IO.MemoryStream stream = new System.IO.MemoryStream(bas.Array, 0, bas.Count))
                    {
                        Formatter formater = new Formatter();
                        Message = formater.Deserialize(stream);
                    }
                }
                catch(Exception e_)
                {
                    throw e_;
                }
                
            }
            public void Save(BufferWriter writer)
            {
                ByteArraySegment bas = ByteArraySegment;
                try
                {
                    
                    using (System.IO.MemoryStream stream = new System.IO.MemoryStream(bas.Array))
                    {
                        Formatter formater = new Formatter();
                        formater.Serialize(stream, Message);
                        bas.SetInfo(0, (int)stream.Position);
                    }
                    writer.Write(bas.Array, 0, bas.Count);
                }
                catch (Exception e_)
                {
                    throw e_;
                }
               
            }

            [ThreadStatic]
            private static ByteArraySegment mByteArraySegment = null;

            public ByteArraySegment ByteArraySegment
            {
                get
                {
                    if (mByteArraySegment == null)
                        mByteArraySegment = new ByteArraySegment(TcpUtils.DataPacketMaxLength);
                    return mByteArraySegment;
                }
            }
        }
    }

分析器主要重写了ReadCast,WriteCast和MessageRead方法,而两个Cast方法主要是把消息进行一个适配器包装到一个IMessage对象中提供给组件处理.通过适配器MessageAdapter来实现终于对象的序列化和反序列化操作,并整合的流中. 有了以上实现那就可以对.NET二制序列化的对象进行处理了,以下制订一个简单的注册对象.

    [Serializable]
    public class Register
    {
        public string UserName { get; set; }
        public string EMail { get; set; }
        public DateTime ResponseTime { get; set; }
    }

实现相应的TCP服务

协议分析器扩展完成后就通过它来实现一个基于对象序列化的TCP交互服务.

    class Program:ServerBase<BinaryFormatterPackage>
    {
        static void Main(string[] args)
        {
            TcpUtils.Setup("beetle");
            Program server = new Program();
            server.Open(9034);
            Console.WriteLine("service start @9034");
            System.Threading.Thread.Sleep(-1);
        }
        protected override void OnConnected(object sender, ChannelEventArgs e)
        {
            base.OnConnected(sender, e);
            Console.WriteLine("{0} connected!", e.Channel.EndPoint);
        }
        protected override void OnDisposed(object sender, ChannelDisposedEventArgs e)
        {
            base.OnDisposed(sender, e);
            Console.WriteLine("{0} disposed!", e.Channel.EndPoint);
        }
        protected override void OnError(object sender, ChannelErrorEventArgs e)
        {
            base.OnError(sender, e);
            Console.WriteLine("{0} error {1}!", e.Channel.EndPoint,e.Exception.Message);
        }
        protected override void OnMessageReceive(PacketRecieveMessagerArgs e)
        {
            base.OnMessageReceive(e);
            Register register = (Register)e.Message;
            register.ResponseTime = DateTime.Now;
            e.Channel.Send(register);
        } 
    }

TCP服务的实现和原来的实现方式一致,只是继承的ServerBase的泛型参是基于对象序列化的协议分析器类型.

连接到服务进行对象通讯

同样接入服务端的代码只是改变一下泛型参类型即可

    private void cmdConnect_Click(object sender, EventArgs e)
    {
        try
        {
            channel = Beetle.TcpServer.CreateClient<BinaryFormatterPackage>(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

总结

组件提供灵活的协议扩展功能,只需做简单的扩展工作就能实现不同序列化方式的对象信息传输.由于.net二进制序列化是自有的序列化方式,如果和其他平台交互信息并不是一个好的选择.在后面章节会介绍扩展Protobuf-net进行对象传输,它相对.net二进制序列化有着更多的优势.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!