粘包的解決-消息定界(轉)

一.引子與協議說明

以前開發了一個項目——車載導航系統。遇到的第一個問題就是硬件設備如何與服務器通訊。html

關鍵在於通訊協議!數組

衆所周知:要想實現通訊,首先通訊雙方就要達成通訊協議。服務器

話很少說,且看協議:架構

————————————————華麗的分割線—————————————————tcp

以上的這些協議說明是否是看得很頭大呢?this

遵循如此這般的通訊協議的硬件設備又如何才能與服務器以及PC順利通訊呢?編碼

還請各位看官稍安勿躁!且聽我娓娓道來!spa

二.基礎知識-TCP與粘包

咱們都知道,互聯網的核心是TCP/IP協議簇。其中TCP(傳輸控制協議)是一種面向鏈接的、可靠的、基於字節流的傳輸層協議。另外咱們大概都聽過一個詞,叫作「粘包」,然而不少人對其內涵不甚了了。其實,粘包問題和TCP密切相關。由於TCP是面向鏈接的並且基於字節流,咱們能夠用一根水管來比喻TCP的工做方式。設計

字節流就跟水流同樣,當兩個消息一塊兒讀取時,你沒法分別出兩者的邊界。3d

三.粘包的解決-消息定界

爲了解決粘包問題,就須要對消息定界。

方法1:文本協議模式

方法2:二進制協議模式

文本協議模式經過在消息尾部加上特殊的標誌來做爲劃分消息的依據;
二進制協議則將消息封裝成消息頭+消息體,經過解析定長消息頭,而後從消息頭中取得消息體長度,進一步解析出消息體
 
——從而粘包的問題獲得瞭解決。

四.回到問題-協議選擇

以上是硬件設備的消息結構,從中咱們既可以看到文本協議的影子也可以看到二進制協議的影子。

由於含有標誌位,因此能夠採用文本協議。

而後咱們將開頭的標誌位做爲消息頭的一部分,剩下的部分都當成消息體,那麼就是一個二進制協議的形式。二進制協議的兩個要求是:1.消息頭定長 2.消息頭中能解析出消息體長度。而這個消息結構是知足這個要求的。

五.文本協議實現示例

複製代碼
    // 摘要:
    // 文本協議助手接口。
    public interface ITextContractHelper
    {
        // 摘要:
        // 消息結束標識符(通過編碼後獲得的字節數組)的集合。
        // 好比通常應用使、用"\0"做爲消息結束標誌,那麼,集合中只有一個元素("\0"的二進制)。
        // 有的應用可能有多個標識符(如"\0"、"\n"及其它)均可以做爲消息的結束標誌,則集合中就有多個元素。
        // 若是設置爲null,引擎則不進行消息完整性識別及構造,每次接收到數據,就直接觸發MessageReceived事件。
        List<byte[]> EndTokens { get; }
    }
複製代碼

文本協議助手接口定義了採用文本協議的最基本的規範——具有消息結束符。接下來咱們來看該接口的一個簡單的實現。

複製代碼
    public class DefaultTextContractHelper : ITextContractHelper
    {        
        public DefaultTextContractHelper(params string[] endTokenStrs)
        {
            this.endTokens = new List<byte[]>(); ;
            if (endTokenStrs == null || endTokenStrs.Length == 0)
            {
                return;
            }

            foreach (string str in endTokenStrs)
            {
                this.endTokens.Add(System.Text.Encoding.UTF8.GetBytes(str));
            }
        }

        private List<byte[]> endTokens;
        public List<byte[]> EndTokens
        {
            get
            {
                return this.endTokens;
            }
        }
    }
複製代碼

其實文本協議的本質就是:消息的結束符通過編碼(好比UTF-8)後獲得的字節數組做爲字節流中識別消息邊界的標誌。

咱們將其注入通訊引擎,引擎便可根據咱們設置的標誌來分割出一個個消息。

  //初始化並啓動客戶端引擎(TCP、文本協議)
   this.tcpPassiveEngine = NetworkEngineFactory.CreateTextTcpPassiveEngine(this.textBox_IP.Text, int.Parse(this.textBox_port.Text), new DefaultTextContractHelper("\0"));

Demo中用「\0」來做爲消息結束標誌。咱們知道,消息結束符是咱們人爲的加到消息尾部,而真正的消息是不具有這樣的內容的。因此在收到消息時咱們須要剔除這個結束符,也就是解封。

複製代碼
 void tcpPassiveEngine_MessageReceived(System.Net.IPEndPoint serverIPE, byte[] bMsg)
{
    string msg = System.Text.Encoding.UTF8.GetString(bMsg); //消息使用UTF-8編碼
    msg = msg.Substring(0, msg.Length - 1); //將結束標記"\0"剔除
    this.ShowMessage(msg);
}
複製代碼

附:文本協議demo源碼下載

 

六.二進制協議Demo實現示例

複製代碼
    // 摘要:
    //     二進制協議助手接口。
    public interface IStreamContractHelper
    {
        // 摘要:
        //     消息頭的長度。
        int MessageHeaderLength { get; }

        // 摘要:
        //     從消息頭中解析出消息體的長度(注意,不是整個消息的長度,而是不包含消息頭的Body的長度)。
        //
        // 參數:
        //   head:
        //     完整的消息頭,長度固定爲MessageHeaderLength
        int ParseMessageBodyLength(byte[] head);
    }
複製代碼

文本協議助手接口定義了採用文本協議的最基本的規範——消息頭定長,從消息頭中可以解析出消息體長度。接下來咱們看一個該接口的簡單實現:消息頭規定爲8個字節,其中頭4個字節存放一個int類型的消息體長度。

複製代碼
    public class StreamContractHelper : IStreamContractHelper
    {
        public int MessageHeaderLength
        {
            get { return 8; }
        }

        public int ParseMessageBodyLength(byte[] head)
        {
            return BitConverter.ToInt32(head, 0);
        }
    }
複製代碼

咱們將其注入通訊引擎,引擎便可識別消息頭、取出消息體長度來分割出一個個消息。

 //初始化並啓動客戶端引擎(TCP、文本協議)
this.tcpPassiveEngine = NetworkEngineFactory.CreateStreamTcpPassivEngine(this.textBox_IP.Text, int.Parse(this.textBox_port.Text), new StreamContractHelper());

附:二進制協議demo源碼下載

 

七.總結

本文旨在介紹文本協議設計的通常方法,經過對於文本協議與二進制協議的本質的掌握,你們就能根據實際的須要來針對性的實現其通訊協議了。你們能夠根據這個通常的方法本身來實現一開始我給出來的衛星定位系統的通訊協議。

有許多須要與硬件設備通訊的應用,諸如與GPS設備通訊、與工廠車間的一些單片機設備通訊、與物聯網設備通訊等等,你們只要掌握了這些設備的通訊協議,或採用文本協議或採用二進制協議,將其實現,那麼通訊的問題就迎刃而解了!

最後,隨着HTML5 WebSocket技術的日益成熟與普及,B/S架構的應用於C/S架構的應用的通訊也逐漸走向大一統。有興趣的朋友能夠參考:打通B/S與C/S!讓HTML5 WebSocket與.NET Socket共用一個服務端!

 

http://www.cnblogs.com/aoyeyuyan/p/5383042.html

相關文章
相關標籤/搜索