1.写此文的目的
使用Netty也有一段时间了,对Netty也有个大概的了解。回想起刚刚使用Netty的时候踩了很多坑,很多Netty的组件也不会使用,或者说用得不够好,不能称之为"最佳实践"。此文的目的便是带领大家使用Netty构建出一个完整的项目,将自己在实际开发经验中整理出的一些最佳实践分享出来,当然这些最佳实践不一定就是真正的最佳实践,只是自己在开发中整理的,或者参考其他优秀的代码一起整理出的,大家如果有什么不同意见或者更好的实践,欢迎大家在评论区分享,大家一起学习一起进步!
2. 项目准备
先奉上完整版代码 zpsw/jt808-netty
开发环境:IDEA+JDK1.8+Maven
使用框架: Netty + Spring Boot + Spring Data JPA
其他工具: lombok(没用过的同学建议了解一下,很方便)
3. 开发过程
3.1.认识JT808协议
3.2.构建编/解码器
3.3.构建业务Handler
3.4.Channel的高效管理方式
3.5.一些改进
复制代码
3.1 认识JT808协议
下面简单介绍一下JT808协议的格式说明,完全版在JT808协议技术规范.pdf
其中消息体属性中我们先只关注消息体长度,不关注其他,分包情况先不考虑。
根据消息头和消息体我们可以抽象出一个最基本的数据结构
@Data
public class DataPacket {
protected Header header = new Header(); //消息头
protected ByteBuf byteBuf; //消息流
@Data
public static class Header {
private short msgId;// 消息ID 2字节
private short msgBodyProps;//消息体属性 2字节
private String terminalPhone; // 终端手机号 6字节
private short flowId;// 流水号 2字节
//获取包体长度
public short getMsgBodyLength() {
return (short) (msgBodyProps & 0x3ff);
}
//获取加密类型 3bits
public byte getEncryptionType() {
return (byte) ((msgBodyProps & 0x1c00) >> 10);
}
//是否分包
public boolean hasSubPackage() {
return ((msgBodyProps & 0x2000) >> 13) == 1;
}
}
}
复制代码
我们可以先将Header解析出来,然后由子类自己解析包体
public void parse() {
try{
this.parseHead();
//验证包体长度
if (this.header.getMsgBodyLength() != this.byteBuf.readableBytes()) {
throw new RuntimeException("包体长度有误");
}
this.parseBody();//由子类重写
}finally {
ReferenceCountUtil.safeRelease(this.byteBuf);//注意释放
}
}
protected void parseHead() {
header.setMsgId(byteBuf.readShort());
header.setMsgBodyProps(byteBuf.readShort());
header.setTerminalPhone(BCD.BCDtoString(readBytes(6)));
header.setFlowId(byteBuf.readShort());
}
protected void parseBody() {
}
复制代码
其中readByte(int length)方法是对ByteBuf.readBytes(byte[] dst)的一个简单封装
public byte[] readBytes(int length) {
byte[] bytes = new byte[length];
this.byteBuf.readBytes(bytes);
return bytes;
}
复制代码
因为没有在Netty官方的Api中找到类似的方法,所以自己定义了一个
另外定义一个方法用于响应重写。
响应重写:
public ByteBuf toByteBufMsg() {
ByteBuf bb = ByteBufAllocator.DEFAULT.heapBuffer();
bb.writeInt(0);//先占4字节用来写msgId和msgBodyProps
bb.writeBytes(BCD.toBcdBytes(StringUtils.leftPad(this.header.getTerminalPhone(), 12, "0")));
bb.writeShort(this.header.getFlowId());
return bb;
}
**
"最佳实践":尽量使用内存池分配ByteBuf,效率相比非池化Unpooled.buffer()高很多,但是得注意释放,否则会内存泄漏
在ChannelPipeLine中我们可以使用ctx.alloc()或者channel.alloc()获取Netty默认内存分配器,
其他地方不一定要建立独有的内存分配器,可以通过ByteBufAllocator.DEFAULT获取,跟前面获取的是同一个(不特别配置的话)。
**
复制代码
这里当我们将响应转化为ByteBuf写出去的时候,此时并不知道消息体的具体长度,所有此时我们先占住位置,回头再来写。
所有的消息都继承自DataPacket,我们挑出一个字段相对较多的-》 位置上报消息
然后我们建立位置上报消息的数据结构,先看位置消息的格式
建立结构如下:
@Data
public class LocationMsg extends DataPacket {
private int alarm; //告警信息 4字节
private int statusField;//状态 4字节
private float latitude;//纬度 4字节
private float longitude;//经度 4字节
private short elevation;//海拔高度 2字节
private short speed; //速度 2字节
private short direction; //方向 2字节
private String time; //时间 6字节BCD
public LocationMsg(ByteBuf byteBuf) {
super(byteBuf);
}
@Override
public void parseBody() {
ByteBuf bb = this.byteBuf;
this.setAlarm(bb.readInt());
this.setStatusField(bb.readInt());
this.setLatitude(bb.readUnsignedInt() * 1.0F / 1000000);
this.setLongitude(bb.readUnsignedInt() * 1.0F / 1000000);
this.setElevation(bb.readShort());
this.setSpeed(bb.readShort());
this.setDirection(bb.readShort());
this.setTime(BCD.toBcdTimeString(readBytes(6)));
}
}
复制代码
所有的消息如果没有自己的应答的话,需要默认应答,默认应答格式如下
@Data
public class CommonResp extends DataPacket {
private short replyFlowId; //应答流水号 2字节
private short replyId; //应答 ID 2字节
private byte result; //结果 1字节
public CommonResp() {
this.getHeader().setMsgId(JT808Const.SERVER_RESP_COMMON);
}
@Override
public ByteBuf toByteBufMsg() {
ByteBuf bb = super.toByteBufMsg();
bb.writeShort(replyFlowId);
bb.writeShort(replyId);
bb.writeByte(result);
return bb;
}
}
复制代码
3.2 构建编/解码器
解码器
前面协议可以看到,标识位为0x7e,所以我们第一个解码器可以用Netty自带的DelimiterBasedFrameDecoder,其中的delimiters自然就是0x7e了。(Netty有很多自带的编解码器,建议先确认Netty自带的不能满足需求,再自己自定义)
经过DelimiterBasedFrameDecoder帮我们截断之后,信息就到了我们自己的解码器中了,我们的目的是将ByteBuf转化为我们前面定义的数据结构。 定义解码器
public class JT808Decoder extends ByteToMessageDecoder {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List