• Beetle在TCP通讯中使用协议分析器和自定义协议对象


            在处理TCP数据的时候我们需要考虑一个粘包的问题,所谓的粘包就是本次接收的数据不一定完整对应对方发送的数据.对方发送的一次数据有可能需要接收多次才能完成,实际要处理的情况要复习一点;为了解决点包问题所以必须要制订数据分析协议来处理,常用的解决方法有两种:一种是基于结束符的方式,而另一种则是在消息头通过一个4字节存储消息大小.

    分包注意细节

    虽然制定处理粘包的方法,但这两种方法在处理上还是要注意几种情况,以下通过一个图来表达几种情况的处理.

    其实最主要关心的是就是分隔符或头描述的内容分别存放在两次receive的数据中.

    实现一个简单的协议分析器

    组件提供以上两种分包处理方式,基础类分别是HeadSizeOfPackage和EofDataOfPackage;通过继续以上两个类就可以简单地实现对象协议的发送和接收;如果以上两者不适合的情况可以从Package派生一个新的协议分析类来满足实际情况的需要. 接下来通过继承HeadSizeOfPackage实现一个简单的对象协议分析器,相关Package实现如下:

        public class HeadSizePackage:Beetle.HeadSizeOfPackage
        {
            public HeadSizePackage(Beetle.TcpChannel channel) : base(channel) { }
    
            private static Dictionary<string, Smark.Core.InstanceHandler> mTypes = new Dictionary<string, Smark.Core.InstanceHandler>(256);
    
            public static void LoadAssembly(System.Reflection.Assembly assembly)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetInterface("Beetle.IMessage") != null && type.IsClass)
                    {
                        mTypes[type.Name] = new Smark.Core.InstanceHandler(type);
                    }
                }
            }
    
            protected override Beetle.IMessage ReadMessageByType(Beetle.BufferReader reader, out object typeTag)
            {
                typeTag = reader.ReadShortString();
                Smark.Core.InstanceHandler handler;
                if (mTypes.TryGetValue((string)typeTag, out handler))
                {
                    return (Beetle.IMessage)handler.Instance();
                }
                return null;
            }
    
            protected override void WriteMessageType(Beetle.IMessage msg, Beetle.BufferWriter writer)
            { 
                writer.WriteShortString(msg.GetType().Name);
            }
    
        }

    继承HeadSizeOfPackage后主要重写两个方法,分别是ReadMessageByType从BufferReader中读取对消息名称并返回具体的消息对象,WriteMessageType则是写入消息名称.两个方法的主要作用是写入消息类型标记和根据标记返回消息对象.制定完成协议分析后要做的事情就是制定对象协议,以下是一个简单注册协议实现:

        class Register : Beetle.IMessage
        {
            public string Name;
            public string EMail;
            public DateTime ResponseTime;
            public void Load(Beetle.BufferReader reader)
            {
                Name = reader.ReadString();
                EMail = reader.ReadString();
                ResponseTime = reader.ReadDate();
            }
            public void Save(Beetle.BufferWriter writer)
            {
                writer.Write(Name);
                writer.Write(EMail);
                writer.Write(ResponseTime);
            }
        }

    构建对象协义的TCP服务端

     在Beetle中构建基于对象协议的TCP服务端也是一件非常简单的事情,只需要Beetle.ServerBase<T>即可,而泛型参则是具体的协议分析器.

        class Program:Beetle.ServerBase<Beetle.Packages.HeadSizePackage>
        {
    
    
            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)
            {
                Register reg = (Register)e.Message;
                reg.ResponseTime = DateTime.Now;
                Console.WriteLine("Name:{0} EMail:{1}", reg.Name, reg.EMail);
                e.Channel.Send(reg);
            }

    和构建普通TCP服务一样,重写相关处理过程方法即可,不过其中一个方法有所不同就是OnMessageReceive,该对象主要包括接收的消息和对应的Socket通道TcpChannel.在之前只定义了一个Register对象消息,在这里就获取相关消息并把ResponseTime设置成当前时间后发还给对应的客户端.

    构建客户端进行消息交互

    客户端的创建则使用TcpServer.CreateClient<T>方法来构建,泛型参是对应协议分析器,具体代码如下:

        channel = Beetle.TcpServer.CreateClient<Beetle.Packages.HeadSizePackage>(txtIPAddress.Text, 9450,OnReceive);
        channel.ChannelDisposed += OnDisposed;
        channel.ChannelError += OnError;
        channel.BeginReceive();
            private void OnReceive(Beetle.PacketRecieveMessagerArgs e)
            {
                Register reg = (Register)e.Message;
                Invoke(new Action<Register>(r => {
                    txtREMail.Text = r.EMail;
                    txtRName.Text = r.Name;
                    txtResponseTime.Text = r.ResponseTime.ToString();
                }), reg);
            }
            private void OnDisposed(object sender, Beetle.ChannelEventArgs e)
            {
                Invoke(new Action<Beetle.ChannelEventArgs>(s => {
                    txtStatus.Text = "disconnect!";
                }), e);
            }
            private void OnError(object sender, Beetle.ChannelErrorEventArgs e)
            {
                Invoke(new Action<Beetle.ChannelErrorEventArgs>(r => {
                    txtStatus.Text = r.Exception.Message;
                }), e);
            }

    构建连接后绑事相关事件,并进入数据接收模式即可.创建连接完成后就可以进行对象协议发送

        Register reg = new Register();
        reg.Name = txtName.Text;
        reg.EMail = txtEMail.Text;
        channel.Send(reg);

    运行效果

     

    下载代码:Code

    总结

    通过Beetle的协议分析器可以简单地解决TCP粘包问题的同时还可以很灵活地支持不同的协议,在后面的章节里会讲述一下如何扩展一个消息配适器实处理.net二制序充列,XML序列化,prorobuf,amf3等数据对象.

    访问Beetlex的Github
  • 相关阅读:
    简单明了理解Java移位运算符
    mybatis(1):入坑篇
    货币格式化
    100 doors
    Object调用静态方法
    Rust入门篇 (1)
    BOM-字节序标记
    【Linux】设定一个能输入中文的英文环境!
    正则表达式验证 输入内容为小数位不超过2位的数值
    创建一个springMVC项目总结
  • 原文地址:https://www.cnblogs.com/smark/p/2720542.html
Copyright © 2020-2023  润新知