前言
何为自定义协定,其实是绝对标准协议来说的,这里次要针对的是应用层协定;常见的规范的应用层协定如 http、ftp、smtp 等,如果咱们在网络通信的过程中不去应用这些标准协议,那就须要自定义协定,比方咱们罕用的 RPC 框架 (dubbo,thrift),分布式缓存(redis,memcached) 等都是自定义协定;本文就来讲讲如何去自定义公有协定,在此之前咱们先考虑一下为什么要自定义协定。
为什么要自定义协定
间接应用规范的协定益处是不言而喻的,我集体了解的几点长处:
- 既然是标准协议阐明曾经成为了规范,这样很多零碎就能够间接对接,无缝集成;
- 协定最重要的一点就是编码解码,标准协议往往有现成的编码解码包,间接拿来应用,缩小开发工夫;
- 有很多围绕标准协议的第三方测试工具,能够很不便的进行测试;
既然有这么多长处那咱们为什么还要去自定义协定,大抵出于以下几点思考:
- 既然是标准协议,往往兼顾的货色比拟多,导致协定数据相对来说比拟大,这样可能在一些谋求性能,流量的零碎中不能容忍;
- 标准协议有很多,没有哪一种协定能够实用任何场景中,所以如果在某个场景中还没有既定的标准协议,这时候会有各种公有协定;
- 自定义协定只有单方约定好数据结构就行,不具备通用性,实践上来说会更加平安一点,当然当初很多标准协议都有平安版本,比方 https,sftp 等等;
以上只是集体的一点了解,欢送大家补充;对于如何去自定义协定,其实能够去多参考一些支流的标准协议或者公有协定,其实有很多共同点能够去借鉴;上面先简略看看那些支流的协定;
支流协定
上面别离看看一些支流的标准协议或者公有协定都是如何去定义本人的数据结构的,对咱们有十分好的借鉴意义;
http 协定
http 协定大家最相熟不过了,全称叫超文本传输协定,整个申请报文能够分为三个局部别离是:申请行,申请报头,申请注释;
- 申请行
GET /test.html HTTP/1.1 (CRLF 换行)
- 申请报头
Accept-Encoding: gzip, deflate
Content-Length: 38
Content-Encoding: gzip
...
申请包头有很多,每一个代表了各自的含意,这边就不一一列出,咱们这里更加关注整个报文的构造;
- 申请注释
这个只有在 POST 申请的时候才有注释,外面寄存业务数据,比方常见的 json 文本串;具体注释的长度能够依据音讯头中的 Content-Length 来决定;
dubbo 协定
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 协定
Redis 的客户端与服务端采纳叫做 RESP(Redis Serialization Protocol)的网络通信协定替换数据,相对来说还是比较简单的,以下是这个协定的个别模式:
*< 参数数量 > CR LF
$< 参数 1 的字节数量 > CR LF
< 参数 1 的数据 > CR LF
...
$< 参数 N 的字节数量 > CR LF
< 参数 N 的数据 > CR LF
以上大抵介绍了三种比拟有代表性的协定,尽管说每种协定都有各自的应用场景,然而如果咱们本人去定义协定,还是有一些相通的货色;
如何自定义协定
上面咱们重点看看去自定义协定有哪些须要咱们关注的点,以下是自己依据本人的了解整顿了如下关注点:
- 残缺的数据包
- 协定号
- 音讯头标识
- 业务数据
- 预留字段
上面别离逐个具体介绍:
残缺的数据包
咱们平时常常讲数据包,然而 TCP 其实只有流的概念,并没有数据包的概念;那很重要的一点就是咱们的程序怎么晓得当初的业务数据曾经承受全副接管完了,能够作为一个残缺的数据包去解决了,如果不去做解决的话就会呈现咱们常说的半包和粘包问题;支流的的解决形式大抵有这么两种:
- 在音讯头部加上数据包长度形容,比方在 http 协定和 dubbo 协定中呈现的 dataLength 字段;
- 用非凡的字符串作为数据包的结尾,这样咱们在承受数据的时候承受到预约的非凡字符串就示意数据包残缺了;
协定号
可能不同的协定有不同的叫法,我这里把它叫做协定号,集体了解就是依据这个协定号,服务器端晓得去执行什么逻辑;比方 http 协定申请行中的 /test.html,dubbo 协定中的服务名 + 版本号,redis 中的具体要执行什么 key;
音讯头标识
这个是否须要还是要看各自的场景,比方 redis 协定足够简略,无需任何标识,所有的货色都是双端约定好的;然而其余很多协定还是有一些须要的,除了下面说到的能够在音讯头中指定 dataLength,其实还有很多其余的货色能够指定比方:
- 业务数据格式:文本格式,json 格局,html 格局等等;
- 压缩格局:可能为了谋求流量包大小对数据包进行压缩,gzip、deflater、snappy 等;
- 加密算法:可能须要对我的业务数据进行加密解决,保障业务数据的安全性 AES、DES 等;
业务数据
业务数据往往在整个数据包中是最大的,同时也是大小可变的局部;咱们下面所做的这些其实都是在为业务数据服务,业务数据须要在网络传输,最重要的一点就是序列化,个别就以下两种形式:
- 文本形式:序列化文本文档 text,或者 json 串,xml 格局等;
- 二进制形式:常见的比方 protobuf,thrift,kyro 等;
预留字段
是否须要预留字段这个得看状况,比方 http 协定整个音讯头是可变的,每一行一个标识,晓得读取到空行,示意音讯头完结上面就是注释了,能够了解为 http 应用了两种形式来保障完整包,音讯头应用特殊字符结尾,注释应用在音讯头中指定 dataLength;这种形式其实它的整个扩展性是十分好的;
另外一种像 dubbo 这样,其实它的头部相当于曾经固定好了 16 个字节,这种状况下是否能够预留几个字节避免前面的变更;
总结
自定义协定其实在咱们真正的工作中还是很少能接触到的,更多的其实还是去实现业务,然而咱们零碎无时无刻不在和各种应用层协定打交道,如果咱们理解了各种协定,在零碎呈现问题时能够做抓包剖析;另外像咱们罕用的数据库中间件、缓存中间件等,都须要对协定都充沛的理解,而后去实现代理。
感激关注
能够关注微信公众号「回滚吧代码」,第一工夫浏览,文章继续更新;专一 Java 源码、架构、算法和面试。