likes
comments
collection
share

入门指南:使用 Netty 解码数据的简明步骤

作者站长头像
站长
· 阅读数 7

记录使用 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);

判断数据是否完整

入门指南:使用 Netty 解码数据的简明步骤

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);