基于Netty的CJT188协议解析与实现

36 阅读3分钟

一、CJT188协议概述

CJT188(城镇建设行业标准)是用于热量表、水表、燃气表等计量仪表的通信协议标准,在物联网表示领域广泛应用。协议特点:

1.基于主从结构的半双工通信

2.采用DL/T645-2007扩展协议框架

3.数据帧采用定长+变长结构

4.支持多种数据类型的编码

二、协议报文结构解析

完整CJT188帧结构如下:

关键字段说明:

1. 地址域:7字节BCD码,表示唯一标识

2. 控制码:定义操作类型(0x91读数据,0xB1写数据等)

3. 数据长度:数据域的实际字节数

4. 数据域:包含DI(数据标识)和具体数据内容

5. 校验和:从地址域到数据域的累加和校验

三、Netty实现方案设计

1. 整体架构

graph TD
    A[Netty Server] --> B[FrameDecoder]
    B --> C[ProtocolDecoder]
    C --> D[BusinessHandler]
D --> E[ProtocolEncoder]

2. 解决粘包/拆包问题

使用LengthFieldBasedFrameDecoder处理变长报文:

public class Cjt188FrameDecoder extends LengthFieldBasedFrameDecoder {
    public Cjt188FrameDecoder() {
        super(1024, 9, 1, 0, 0);
        // 长度字段偏移量9(地址域7+帧头2)
        // 长度字段长度1字节
    }
}

3. 协议编解码器实现

协议解码器:

public class Cjt188Decoder extends MessageToMessageDecoder<ByteBuf> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        // 验证起始符
        byte startFlag1 = in.readByte();
        if (startFlag1 != (byte)0xFE) {
            throw new InvalidFrameException("Invalid start flag");
        }

        // 读取地址域
        byte[] address = new byte[7];
        in.readBytes(address);

        // 验证第二个起始符
        byte startFlag2 = in.readByte();
        if (startFlag2 != (byte)0xFE) {
            throw new InvalidFrameException("Invalid second start flag");
        }

        // 读取控制码
        byte controlCode = in.readByte();

        // 读取数据长度
        int dataLength = in.readUnsignedByte();
        // 读取数据域
        byte[] data = new byte[dataLength];
        in.readBytes(data);

        // 校验和验证
        byte checksum = in.readByte();
        if (!validateChecksum(in, checksum)) {
            throw new InvalidFrameException("Checksum error");
        }

        // 构建协议对象
        Cjt188Message msg = new Cjt188Message(address, controlCode, data);
        out.add(msg);
    }
}

协议编码器:

public class Cjt188Encoder extends MessageToByteEncoder<Cjt188Message> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Cjt188Message msg, ByteBuf out) {
        out.writeByte(0xFE); // 起始符1
        out.writeBytes(msg.getAddress()); // 地址域
        out.writeByte(0xFE); // 起始符2
        out.writeByte(msg.getControlCode());
        out.writeByte(msg.getData().length);
        out.writeBytes(msg.getData());
        out.writeByte(calculateChecksum(out));
        out.writeByte(0x16); // 结束符
    }
}

4. 业务处理器实现

public class Cjt188BusinessHandler extends SimpleChannelInboundHandler<Cjt188Message> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Cjt188Message msg) {
        switch (msg.getControlCode()) {
            case 0x91: // 读数据请求
                handleReadRequest(ctx, msg);
                break;
            case 0xB1: // 写数据请求
            
                handleWriteRequest(ctx, msg);
                break;

            // 其他控制码处理...

        }

    }
    
    private void handleReadRequest(ChannelHandlerContext ctx, Cjt188Message msg) {

        // 解析DI数据标识

        byte[] data = msg.getData();

        String di = bytesToHex(data, 0, 4); // 前4字节为DI

        // 查询对应数据值

        Object value = queryData(di);

        // 构建响应报文

        Cjt188Message response = buildResponse(msg, value);

        ctx.writeAndFlush(response);

    }
}

四、关键处理逻辑实现

1. 数据域解析(以累积热量为例)

DI码表对应关系:

解析实现:

public class DataParser {
    public static Object parseData(byte[] data) {
        String di = bytesToHex(data, 0, 4);
        switch(di) {
            case "00010000":
                return parseHeatValue(data);
            // 其他DI处理...

        }
    }
   
    private static Long parseHeatValue(byte[] data) {

        ByteBuffer buffer = ByteBuffer.wrap(data, 4, 4)

                         .order(ByteOrder.LITTLE_ENDIAN);

        return buffer.getInt() & 0xFFFFFFFFL;
    }
}

2. 心跳维持机制

public class HeartbeatHandler extends IdleStateHandler {
    public HeartbeatHandler() {
        super(0, 0, 60); // 60秒无读写触发
    }

    @Override
    protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) {
        // 发送心跳帧
        Cjt188Message heartbeat = new Cjt188Message();
        heartbeat.setControlCode(0x13);
        ctx.writeAndFlush(heartbeat);
    }
}

3. 异常处理机制

public class ExceptionHandler extends ChannelDuplexHandler {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (cause instanceof InvalidFrameException) {
            log.warn("Invalid frame received: {}", cause.getMessage());
            sendErrorResponse(ctx, 0xE1); // 非法数据错误
        } else {
            ctx.close();
        }
    }
}

五、服务端启动配置

public class Cjt188Server {
    public void start(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) {
                     ch.pipeline()
                       .addLast(new Cjt188FrameDecoder())
                       .addLast(new Cjt188Decoder())
                       .addLast(new Cjt188Encoder())
                       .addLast(new HeartbeatHandler())
                       .addLast(new Cjt188BusinessHandler())
                       .addLast(new ExceptionHandler());
                 }
             });
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}