如何自定义通信协议

前言

在编写内部系统网络通信的代码时,往往需要自定义通信协议,因为目前通用的网络通信协议显得比较笨重,不够轻量级,例如:http协议等。自定义通信协议,可以设计的更小巧,更轻量级,也节省网络带宽。那么如何自定义通信协议呢?本篇主要是总结常用的通信协议设计规则。

问题现状

应用层通信协议基本都是基于TCP传输协议的,因为TCP协议有慢启动,消息确认,失效重发等特性,能够保证消息准确的投递到另一端。但是由于TCP是面向字节流的,消息之间是没有边界的,这样就会出现所谓的TCP粘包,读半包,写半包等问题,导致应用程序拿的可能并不是一个完整的消息数据。

如何解决粘包的问题?

image

本质上是要在应用层维护消息与消息的边界问题。

  1. 方案1:消息固定长度。要求在应用层强制指定消息的长度,长度不够进行补足,长度要是超过固定长度大小则需要进行拆分成多个消息进行传输,这样接收端才能够很好的确定消息边界,从而对TCP字节流进行解析。

  2. 方案2:消息之间有特殊的分隔符。给消息定义特殊的结束符,例如,FTP协议每个消息都以回车换行符CRLF(/r/n)结束,这样也可以明确消息的边界,但是如果消息内容中也含有CRLF,则会导致消息解析失败问题。

  3. 方案3:请求头定长,请求体变长。请求头部长度固定,其中在头部有4个字节,表示body部分长度。

    byte cmd; // 消息类型 1字节消息类型
    long msgId; //消息id 8字节消息Id
    int bodyLen; // 4字节body长度
    // body部分
    ........
    ..........[body部分]
    ............

    这样固定的请求头长度:13字节,请求体body变长。

  4. 方案4:请求头变长,请求体变长。例如,http协议就是这种,在请求头中含有Content-Length指定请求body的数据长度,同时请求头也是变化的(这里的头部包括:请求行和所有的请求头信息),但是http协议的头部又是类似ftp协议一样可以区分的,请求行及请求头之间都是使用CRLF分开的,所以整体上每个http消息也是可以拆分的。

    GET /api/users/1 HTTP/1.1/r/n
    Content-Length: 100/r/n
    Content-Type: application/json/r/n
    Host: 127.0.0.1/r/n
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)/r/n
    /r/n
    // body部分
    ........
    ...........[body部分]
    .............

总结

通过上面的分析不难发现如下规律:

  1. 方案3 = 请求头(方案1)+ 请求头部加上body长度字段;
  2. 方案4 = 请求头(方案2)+ 请求头部加上body长度字段;