入门指南:使用 Netty 解码数据的简明步骤
记录使用 netty 的过程,本人是一个菜鸟。
废话不多说直接开始
业务是程序需要接收两个互联网设备的数据,数据格式不一样,设备A是String,设备B是需要将每个字节进行解析。
实现处理多个设备过程
思路:我们定义一个转接处理器 TypeHandle。
public class WSServerInitializer extends ChannelInitializer<NioSocketChannel> {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) {
nioSocketChannel.pipeline().addLast(new TypeHandle());
}
}
在这里继承 ChannelInitializer,它是一个父类方法。重写 channelRead 进行处理数据。
通过 ctx.pipeline().addLast(new StringHandle()); 进行添加处理器,通过ctx.fireChannelRead(str);进行数据转发。这样我们就可以在符合目标的处理器中处理数据了。字符串数据直接通过转字符串就可以了,不过多介绍这一点了。
public class TypeHandle extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String str = ((ByteBuf) msg).toString(StandardCharsets.UTF_8);
if (str.startsWith("*CS") && str.endsWith("#")) {
ctx.pipeline().addLast(new StringHandle());
ctx.fireChannelRead(str);
} else {
ctx.pipeline().addLast(new ByteDecodeHandle());
ctx.fireChannelRead(msg);
}
}
}
看一下 StringHandle 的类,继承 SimpleChannelInboundHandler 类,并泛型指定 String。
SimpleChannelInboundHandler 是 Netty 框架中用于处理特定类型消息(字符串类型)的处理器。它是 Netty 中的一个泛型类,用于处理入站数据
public class StringHandle extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) {
}
}
处理字节数据
首先我们先认识一下 ByteToMessageDecoder
解码器,用于将字节解码为消息对象,在网路通信中,数据是以字节流形式进行传输的,而 ByteToMessageDecoder 作用是实现了这一个过程。
触发时机
在自定义的 ByteDecodeHandle 类中我们继承了 ByteToMessageDecoder
,所以当设备数据发送之后,会自动进入 decode 进行解码数据。decode 方法就是我们的解码方法,在这里我们主要对 ByteBuf 进行处理,它就是我们的数据。
public class ByteDecodeHandle extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
}
}
crc 校验
需要将数据传入指定算法中,首先 COPY 一个 bytebuf,因为读数据时,bytebuf中的指针会位移。不包括最后的2个字节数据,这是根据实际业务规定的。最终通过 calculateCRC16 进行校验。因为是 COPY 的 bytebuf,最后要释放一下资源,通过 release
ByteBuf byteBuf = in.copy();
// 校验 CRC
// 创建一个字节数组,长度为 copiedByteBuf 的可读字节数减去2(不包括倒数第二个字节)
byte[] byteArray = new byte[byteBuf.readableBytes() - 2];
// 从 copiedByteBuf 中读取数据到字节数组中,从0读到倒数第二个字节
byteBuf.readBytes(byteArray);
// 释放 copiedByteBuf
byteBuf.release();
int i = CRC16.calculateCRC16(byteArray);
判断数据是否完整
getShortLE 是 bytebuf 的方法,用于获取小端序2个字节数据,参数6表示字节偏移6,可以看上面文档。通过 readableBytes 方法获取可读长度,如果小于数据长度,表示当前数据不完整。
// 解析数据长度
short dataLength = in.getShortLE(6);
// 判断数据是否完整
if (in.readableBytes() < dataLength) {
System.out.println("数据不完整!");
}
读取 IMEI 16位字节的呢
由于 IMEI 是16位的字节数,我目前业务的实现方式是,将字节数组每一位转成 char,最后拼接起来转换成数字。
// IMEI,读入 16位
byte[] dst = new byte[16];
in.getBytes(26, dst);
StringBuilder sb = new StringBuilder();
// 将字节数组转换为字符串
for (byte b : dst) {
if (b == 0) break;
sb.append((char) b);
}
// 将字符串解析为数字
Long IMEI = Long.parseLong(sb.toString());
最后说一下读字节的方式,去除 LE 表示大端序
# 获取四个字节
int productSN = in.getIntLE(8);
# 获取一个字节
byte deviceType = in.getByte(12);
# 获取一个字节,结果是 char
char hardwareVersion = (char) in.getByte(13);
# 获取两个字节
short backgroundAngle = in.getShortLE(18);
转载自:https://juejin.cn/post/7353234023098056723