交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

一.引子与协议说明

      之前开发了一个项目——车载导航系统。遇到的第一个问题就是硬件设备如何与服务器通信。

      关键在于通信协议!

      众所周知:要想实现通信,首先通信双方就要达成通信协议。

      话不多说,且看协议:

      交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

   交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

 交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

   交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

 ————————————————华丽的分割线—————————————————

      以上的这些协议说明是不是看得很头大呢?

      遵循如此这般的通信协议的硬件设备又如何才能与服务器以及PC顺利通信呢?

      还请各位看官稍安勿躁!且听我娓娓道来!       

二.基础知识-TCP与粘包     

     我们都知道,互联网的核心是TCP/IP协议簇。其中TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层协议。另外我们大概都听过一个词,叫做“粘包”,然而很多人对其内涵不甚了了。其实,粘包问题和TCP密切相关。因为TCP是面向连接的而且基于字节流,我们可以用一根水管来比喻TCP的工作方式。

     交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载) 

      字节流就跟水流一样,当两个消息一起读取时,你无法分别出二者的边界。 

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

     为了解决粘包问题,就需要对消息定界。

方法1:文本协议模式

交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

方法2:二进制协议模式

交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

     文本协议模式通过在消息尾部加上特殊的标志来作为划分消息的依据;二进制协议则将消息封装成消息头+消息体,通过解析定长消息头,然后从消息头中取得消息体长度,进一步解析出消息体——从而粘包的问题得到了解决。 

四.回到问题-协议选择

 交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

    以上是硬件设备的消息结构,从中我们既能够看到文本协议的影子也能够看到二进制协议的影子。

    因为含有标志位,所以可以采用文本协议。

    然后我们将开头的标志位作为消息头的一部分,剩下的部分都当成消息体,那么就是一个二进制协议的形式。二进制协议的两个要求是:1.消息头定长 2.消息头中能解析出消息体长度。而这个消息结构是满足这个要求的。

  交通运输部·车载导航系统——终端如何与服务器通信——玩转通信协议(源码下载)

五.文本协议实现示例

    // 摘要:
    // 文本协议助手接口。
    public interface ITextContractHelper
    {
        // 摘要:
        // 消息结束标识符(经过编码后得到的字节数组)的集合。
        // 比如一般应用使、用" "作为消息结束标志,那么,集合中只有一个元素(" "的二进制)。
        // 有的应用可能有多个标识符(如" "、"
"及其它)都可以作为消息的结束标志,则集合中就有多个元素。
        // 如果设置为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(" "));

Demo中用“