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等數據對象.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM