Netty 通讯协议设计概要
1. 前言
上节内容,我们主要介绍了 Netty 的粘包和拆包问题,并且大致介绍了 Netty 提供的常见拆包器,分别是固定长度拆包器、行拆包器、分隔符拆包器、基于长度域拆包器,但是它们只是相对简单的协议,也就是说无法满足复杂的业务场景,因此,我们可以通过自定义协议的方式去解决 TCP 的粘包和拆包问题。
2. 了解什么是协议
首先,我们大概了解什么是协议,协议可以把它认为是一种规则而不是技术,约束客户端和服务端之间通讯,数据组装和拆分的一种规范。客户端安装某种规范去组装数据,把数据传输给服务端,服务端再安装这种规范拆解数据,那么这就是一种协议,可以根据实际业务区指定符合自身的协议,其实基于 Netty 去制定的私有协议,我个人接触过的是传输车辆 GPS 数据的 809 协议,在和 GPS 服务器通讯时,必须按照该协议去进行封装和解析数据,否则通讯异常。
其实,类似的规则还有很多,从开发的角度来说,都是各种规则和约束,比如说:前面提到的序列化技术,序列化其实就是把数据按照某种规则去转换成 byte 数字,而反序列化就是按照这种规则再去把字节流转换成对应的类型数据。这些都是基于某种规则的基础上,使用技术的手段去封装的结果。
3. 通讯协议
3.1 协议架构
思路架构图:
首先,我们先来了解协议在整个通讯当中的扮演的角色,如下图所示:
如上图所示,客户端和服务端之间的通讯流程:
客户端发送数据
- 客户端先把一个对象序列化成字节流;
- 然后把字节流根据协议把字节流组装好;
- 最后转换成二进制传输到网络。
服务端接受数据
- 从网络中读取二进制数据到本地的缓冲区;
- 根据协议的规则读取指定数据,并且识别是否是完整的数据包;
- 如果是完整的数据包,则转换成实体对象。
由此可见,协议主要是管理字节流格式的一种规则,如果把协议环节去掉,那么服务端就无法知道字节流的结束位置。
3.2 协议设计
协议介绍:
- 协议标识符,以一个固定数作为标识符,占用 4 个字节,主要目的是用来识别协议的开头,只要是以该标识开头的协议则进行处理,否则不处理。主要目的是提高处理性能问题,如果随便一个请求都需要进行处理,但是最终处理起来发现协议格式不对,抛异常,肯定会影响系统性能;
- 数据长度,占用 4 个字节,标识数据的真实长度,获取到该值后,往后读取指定长度的数据即可。主要目的是防止粘包和拆包安全性问题;
- 指令,协议是某个应用所有的业务公用的一种规则,那么应该如何区分是哪种业务呢?这里主要通过指令来进行区分;
- 数据,这部分存储的是真实的数据。
这算是比较简单,并且常用的设计思路,主要和 Netty 内置的基于长度域拆包器类似,基本上都是有一个字段是用来存储真实的数据长度,这样才能准确的读取数据的完整内容。当然,还可以在该设计基础上加上更多的字段,比如:使用的序列号技术、协议版本号等等。
3.3 技术栈说明
相信到这里,大家对协议基本上有一个简单的认识了,其实协议并不难,它只是一个约束而已,那么我们如何通过技术的实现,让协议生效呢?主要的核心思想如下:
- 序列化和发序列话技术,这个在 Netty 的编解码的时候已经讲过,序列化可以把对象转化成字节流,反序列化可以把字节流转换成对象;
- 字节容器(字节缓冲区),必须按照协议的字段顺序往字节容器里面存放对应的字节内容,然后把整个容器写到网络当中。这样数据才能按照顺序进行传输,服务端才能按照顺序进行数据的读取和处理。
4. 小结
本节主要介绍协议的思想,它就是一种规则,客户端和服务端必须需要遵守的规则,才能保证数据的安全性。其次,讲解了协议在客户端和服务端通讯当中所扮演的角色,如果没有协议,那么服务端一直读取字节流,根本无法知道数据的完整性。最后,大概介绍了协议设计的大概思想,主要核心字段有四个,分别是协议标识符、数据长度、指令、数据,这四个字段是满足协议的基本元素,可以根据实际业务再进行扩展字段。