使用netty不是一天两天了,但是使用netty和DTU通讯还是第一次,接下来要做DTU的通讯协议,对接工作还没有正式开始,只收到一个简单的DTU协议文档,里面的内容大概是下面表格中的样子。
位数
内容
取值
0
字头
0XCC
1
数据长度
低八位
2
数据长度
高八位
3
类型
0:心跳,1:登录
4
机器编码
0X00-0XFF
5
校验位
前面字节相加的低八位
这里把通讯协议简化了一下,仅剩下心跳和登录,如果有其他参数可以在此基础上进行扩展;从表格中可以看出来,每个字段数据位数还不一样,有的一位,有的两位,数据长度占两位,其他各占用一位。
只有这么一个文档,只有这么一丁点信息,其他就什么也不知道了,这可如何是好?不知道从哪里下手,这个疑问多多的项目就这样放了一段时间。
但是也不能总是这样放着呀,如果对接的人来了,我这边什么也没有,一下子也建不起一个项目呀?转念又想,曾经使用 **netty + google protobuf ** 开发过IM项目,也有些相似之处。这个DTU可否使用google protobuf呢?
于是,写了一个简单的客户端,一个服务端,来进行收发信息,google protobuf是通过对象编码成二进制进行数据通讯的,但文档中是字节数组,压根没有对象一说呀?写完了demo,测试一遍,但是和字节数组对应不起来,最后还是删掉了。
在网上找了很多资料,找来找去只找到这么两篇Java采用Netty实现基于DTU的TCP服务器 + 多端口 + 多协议
Java 使用Socket 实现基于DTU的TCP服务器 + 数据解析 + 心跳检测可以参考,试着把上面的demo扒拉了好几遍,通过这两篇文章,可以获得一些信息:
第一,可以使用netty和dtu进行通信,选择使用netty框架没有错;
第二,和dtu对接,接收到的是字节数组,不能使用google的protobuf框架,需要另做处理。
把参考文档中带netty的demo也试着在本地拷贝了一份,大概知道了对接收到的字节数组如何处理,但是demo中只有接收,没有发送,这是不够完美的;这也是个问题,单方面的,不好运行呀。
另外在处理字节数组的时候,在流程上还不是太标准,后面有可能会遇到半包、粘包的问题,这些都是需要面对的问题。结合自己曾经开发过IM的经验,把编解码处理和数据处理也分离出来,半包、粘包的问题一并考过进去,这样后面再完善就方便多了。
下面就开始SpringBoot2.1.4 + netty + DTU的客户端编码,难点就在于字节数组的解码和编码,一进一出,搞定了这一步,其他的业务逻辑就好处理了。这里的客户端编码是为了测试DTU的请求,以便和服务端互动起来,可以进行测试。
第一步,pom文件引入netty架包,就这两个架包足够用的了;
org.springframework.boot
spring-boot-starter
io.netty
netty-all
第二步,对字节数组的编解码,包括半包、粘包处理;
字节数据解码类ByteArrayDecoder:
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.example.instant.ProtoInstant;
import com.example.util.CharacterConvert;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
/**
* byte 1字节 (8位) -27~27-1 0 Byte 255
* short 2字节 (16位) -215~215-1 0 Short
* int 4字节 (32位) -231~ 231-1 0 Integer
* long 8字节 (64位) -263~263-1 0 Long
* char 2字节 (C语言中是1字节)可以存储一个汉字
* float 4字节 (32位) -3.4e+38 ~ 3.4e+38 0.0f Float
* double 8字节 (64位) -1.7e+308 ~ 1.7e+308 0 Double
* char 2字节(16位) u0000~uFFFF(‘’~‘?’) ‘0’ Character (0~216-1(65535))
* 布尔 boolean 1/8字节(1位) true, false FALSE Boolean
* C语言中,short、int、float、long、double,分别为:1个、2个、4个、8个、16个
* 对字节数组进行解码
* @author 程就人生
* @date 2020年8月3日
* @Description
*
*/
public class ByteArrayDecoder extends ByteToMessageDecoder{
private static Logger log = LoggerFactory.getLogger(ByteArrayDecoder.class);
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
// 标记一下当前的readIndex的位置
in.markReaderIndex();
//判断获取到的数据是否够字头,不沟通字头继续往下读
//字头:1位,数据串总长度:2位
if(in.readableBytes() < ProtoInstant.FILED_LEN){
log.info("不够包头,继续读!");
return;
}
//读取字头1位
int fieldHead = CharacterConvert.byteToInt(in.readByte());
if(fieldHead != ProtoI