何为自定义协议,其实是相对标准协议来说的,这里主要针对的是应用层协议;常见的标准的应用层协议如http、ftp、smtp等,如果我们在网络通信的过程中不去使用这些标准协议,那就需要自定义协议,比如我们常用的RPC框架(dubbo,thrift),分布式缓存(redis,memcached)等都是自定义协议;本文就来讲讲如何去自定义私有协议,在此之前我们先考虑一下为什么要自定义协议。
直接使用标准的协议好处是显而易见的,我个人理解的几点优点:
既然有这么多优点那我们为什么还要去自定义协议,大致出于以下几点考虑:
以上只是个人的一点理解,欢迎大家补充;关于如何去自定义协议,其实可以去多参考一些主流的标准协议或者私有协议,其实有很多共同点可以去借鉴;下面先简单看看那些主流的协议;
下面分别看看一些主流的标准协议或者私有协议都是如何去定义自己的数据结构的,对我们有非常好的借鉴意义;
http协议大家最熟悉不过了,全称叫超文本传输协议,整个请求报文可以分为三个部分分别是:请求行,请求报头,请求正文;
GET /test.html HTTP/1.1 (CRLF换行)
Accept-Encoding: gzip, deflate
Content-Length: 38
Content-Encoding: gzip
...
请求包头有很多,每一个代表了各自的含义,这边就不一一列出,我们这里更加关注整个报文的结构;
dubbo协议格式可以直接参考官网提供的如下图片:
看上图其实整个协议数据包也大致分为两个部分:固定部分和可变部分,或者叫消息头和消息体;
固定部分一共是4+8+4=16个字节,具体如下所示:
header{
Magic High = 8bit; //魔数高位
Magic Low = 8bit; //魔数低位
Req/Res = 1bit; //标识是请求或响应
2 Way = 1bit; //标记是否期望从服务器返回值
Event = 1bit; //标识是否是事件消息
Serialization ID = 5bit; //标识序列化类型
Status = 8bit; //标识响应的状态
Request ID = 64bit; //标识唯一请求
Data Length = 32bit; //序列化后的内容长度
}
可变部分根据固定部分中的Data Length来确定长度;
Redis的客户端与服务端采用叫做 **RESP(Redis Serialization Protocol)**的网络通信协议交换数据,相对来说还是比较简单的,以下是这个协议的一般形式:
*< 参数数量 > CR LF
$< 参数 1 的字节数量 > CR LF
< 参数 1 的数据 > CR LF
...
$< 参数 N 的字节数量 > CR LF
< 参数 N 的数据 > CR LF
以上大致介绍了三种比较有代表性的协议,虽然说每种协议都有各自的使用场景,但是如果我们自己去定义协议,还是有一些相通的东西;
下面我们重点看看去自定义协议有哪些需要我们关注的点,以下是本人根据自己的理解整理了如下关注点:
下面分别逐一详细介绍:
我们平时经常讲数据包,但是TCP其实只有流的概念,并没有数据包的概念;那很重要的一点就是我们的程序怎么知道现在的业务数据已经接受全部接收完了,可以作为一个完整的数据包去处理了,如果不去做处理的话就会出现我们常说的半包和粘包问题;主流的的处理方式大致有这么两种:
可能不同的协议有不同的叫法,我这里把它叫做协议号,个人理解就是根据这个协议号,服务器端知道去执行什么逻辑;比如http协议请求行中的/test.html,dubbo协议中的服务名+版本号,redis中的具体要执行什么key;
这个是否需要还是要看各自的场景,比如redis协议足够简单,无需任何标识,所有的东西都是双端约定好的;但是其他很多协议还是有一些需要的,除了上面说到的可以在消息头中指定dataLength,其实还有很多其他的东西可以指定比如:
业务数据往往在整个数据包中是最大的,同时也是大小可变的部分;我们上面所做的这些其实都是在为业务数据服务,业务数据需要在网络传输,最重要的一点就是序列化,一般就以下两种方式:
是否需要预留字段这个得看情况,比如http协议整个消息头是可变的,每一行一个标识,知道读取到空行,表示消息头结束下面就是正文了,可以理解为http使用了两种方式来保证完整包,消息头使用特殊字符结尾,正文使用在消息头中指定dataLength;这种方式其实它的整个扩展性是非常好的;
另外一种像dubbo这样,其实它的头部相当于已经固定好了16个字节,这种情况下是否可以预留几个字节防止后面的变更;
自定义协议其实在我们真正的工作中还是很少能接触到的,更多的其实还是去实现业务,但是我们系统无时无刻不在和各种应用层协议打交道,如果我们了解了各种协议,在系统出现问题时可以做抓包分析;另外像我们常用的数据库中间件、缓存中间件等,都需要对协议都充分的了解,然后去实现代理。
可以关注微信公众号「回滚吧代码」,第一时间阅读,文章持续更新;专注Java源码、架构、算法和面试。