一、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();
}
}
}