20260107:
1、对Session优化,存在下发指令失败问题
This commit is contained in:
parent
abb3058fd8
commit
ab82d28a2e
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.compile.nullAnalysis.mode": "automatic"
|
||||||
|
}
|
||||||
@ -40,6 +40,11 @@ public class Session {
|
|||||||
* 这里必须保证并发可见性与线程安全:Netty IO线程会调用 response(),而业务线程可能在发起 request()。
|
* 这里必须保证并发可见性与线程安全:Netty IO线程会调用 response(),而业务线程可能在发起 request()。
|
||||||
*/
|
*/
|
||||||
private final Map<String, MonoSink> topicSubscribers = new ConcurrentHashMap<>();
|
private final Map<String, MonoSink> topicSubscribers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求被拒绝时返回的 Mono
|
||||||
|
*/
|
||||||
|
private static final Mono rejected = Mono.error(new RejectedExecutionException("重复的request订阅"));
|
||||||
/**
|
/**
|
||||||
* 是否存在等待中的 requestR()。
|
* 是否存在等待中的 requestR()。
|
||||||
* 用于入站侧判断是否需要尝试 response 匹配(避免无意义的匹配开销,也方便业务侧做更精细的路由)。
|
* 用于入站侧判断是否需要尝试 response 匹配(避免无意义的匹配开销,也方便业务侧做更精细的路由)。
|
||||||
@ -259,27 +264,31 @@ public class Session {
|
|||||||
* 订阅回调 mono.doOnSuccess(处理成功).doOnError(处理异常).subscribe(开始订阅)
|
* 订阅回调 mono.doOnSuccess(处理成功).doOnError(处理异常).subscribe(开始订阅)
|
||||||
*/
|
*/
|
||||||
public <T> Mono<T> request(Message message, Class<T> responseClass) {
|
public <T> Mono<T> request(Message message, Class<T> responseClass) {
|
||||||
final String key = requestKey(message, responseClass);
|
String key = requestKey(message, responseClass);
|
||||||
|
Mono<T> subscribe = this.subscribe(key);
|
||||||
// 关键修复:必须“先注册等待回调,再发送数据”
|
if (subscribe == null) {
|
||||||
// 否则设备极快回包时,response() 可能发生在订阅登记之前,导致误判为普通上报并落库。
|
return rejected;
|
||||||
return Mono.<T>create(sink -> {
|
|
||||||
MonoSink existing = topicSubscribers.putIfAbsent(key, sink);
|
|
||||||
if (existing != null) {
|
|
||||||
sink.error(new RejectedExecutionException("重复的request订阅:" + key));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelFuture channelFuture = channel.writeAndFlush(Packet.of(this, message));
|
ChannelFuture channelFuture = channel.writeAndFlush(Packet.of(this, message));
|
||||||
channelFuture.addListener(future -> {
|
return Mono.create(sink -> channelFuture.addListener(future -> {
|
||||||
if (!future.isSuccess()) {
|
if (future.isSuccess()) {
|
||||||
// 发送失败,移除订阅并返回错误
|
sink.success(future);
|
||||||
topicSubscribers.remove(key);
|
} else {
|
||||||
sink.error(future.cause());
|
sink.error(future.cause());
|
||||||
}
|
}
|
||||||
|
})).then(subscribe).doFinally(signal -> unsubscribe(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅等待响应
|
||||||
|
*/
|
||||||
|
private <T> Mono<T> subscribe(String key) {
|
||||||
|
if (topicSubscribers.containsKey(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Mono.create(sink -> {
|
||||||
|
topicSubscribers.put(key, sink);
|
||||||
});
|
});
|
||||||
})
|
|
||||||
.doFinally(signal -> unsubscribe(key));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,7 +2,10 @@ package com.fastbee.server.handler;
|
|||||||
|
|
||||||
|
|
||||||
import com.fastbee.base.codec.Delimiter;
|
import com.fastbee.base.codec.Delimiter;
|
||||||
|
import com.fastbee.base.session.Session;
|
||||||
|
import com.fastbee.base.util.AttributeUtils;
|
||||||
import com.fastbee.base.util.ByteBufUtils;
|
import com.fastbee.base.util.ByteBufUtils;
|
||||||
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import com.fastbee.common.utils.gateway.CRC16Utils;
|
import com.fastbee.common.utils.gateway.CRC16Utils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@ -62,9 +65,30 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备标识(用于日志)
|
||||||
|
*/
|
||||||
|
private String getDeviceId(ChannelHandlerContext ctx) {
|
||||||
|
Session session = AttributeUtils.getSession(ctx.channel());
|
||||||
|
if (session != null && session.getClientId() != null) {
|
||||||
|
return session.getClientId();
|
||||||
|
}
|
||||||
|
// 未注册时使用远程地址
|
||||||
|
return String.valueOf(ctx.channel().remoteAddress());
|
||||||
|
}
|
||||||
|
|
||||||
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) {
|
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) {
|
||||||
|
// 调试:记录收到的原始数据
|
||||||
|
if (log.isDebugEnabled() && buffer.readableBytes() > 0) {
|
||||||
|
byte[] temp = new byte[Math.min(buffer.readableBytes(), 100)];
|
||||||
|
buffer.getBytes(buffer.readerIndex(), temp);
|
||||||
|
log.debug("[{}] 解码器收到数据: {},长度={}", getDeviceId(ctx), ByteBufUtil.hexDump(temp), buffer.readableBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
String deviceId = getDeviceId(ctx);
|
||||||
|
|
||||||
// 优先级1:检测7E帧格式(注册包/心跳包)
|
// 优先级1:检测7E帧格式(注册包/心跳包)
|
||||||
int frame7EResult = check7EFrame(buffer);
|
int frame7EResult = check7EFrame(buffer, deviceId);
|
||||||
if (frame7EResult > 0) {
|
if (frame7EResult > 0) {
|
||||||
// 是7E帧且数据完整,读取并返回
|
// 是7E帧且数据完整,读取并返回
|
||||||
return buffer.readRetainedSlice(frame7EResult);
|
return buffer.readRetainedSlice(frame7EResult);
|
||||||
@ -74,11 +98,11 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 优先级2:检测Modbus RTU帧格式(避免数据中的7E被当作分隔符)
|
// 优先级2:检测Modbus RTU帧格式(避免数据中的7E被当作分隔符)
|
||||||
int modbusResult = checkModbusRtu(buffer);
|
int modbusResult = checkModbusRtu(buffer, deviceId);
|
||||||
if (modbusResult > 0) {
|
if (modbusResult > 0) {
|
||||||
// 是Modbus帧且数据完整,读取并返回
|
// 是Modbus帧且数据完整,读取并返回
|
||||||
ByteBuf frame = buffer.readRetainedSlice(modbusResult);
|
ByteBuf frame = buffer.readRetainedSlice(modbusResult);
|
||||||
log.debug("成功解析Modbus RTU帧,长度={}", modbusResult);
|
log.debug("[{}] 成功解析Modbus RTU帧,长度={}", deviceId, modbusResult);
|
||||||
return frame;
|
return frame;
|
||||||
} else if (modbusResult == 0) {
|
} else if (modbusResult == 0) {
|
||||||
// 是Modbus帧但数据不完整,等待更多数据,不要用分隔符处理
|
// 是Modbus帧但数据不完整,等待更多数据,不要用分隔符处理
|
||||||
@ -86,6 +110,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 优先级3:使用分隔符处理(兜底方案,仅当不是7E帧也不是Modbus帧时)
|
// 优先级3:使用分隔符处理(兜底方案,仅当不是7E帧也不是Modbus帧时)
|
||||||
|
log.debug("[{}] 非7E帧非Modbus帧,尝试分隔符处理,7E结果={}, Modbus结果={}", deviceId, frame7EResult, modbusResult);
|
||||||
int minFrameLength = Integer.MAX_VALUE;
|
int minFrameLength = Integer.MAX_VALUE;
|
||||||
Delimiter minDelim = null;
|
Delimiter minDelim = null;
|
||||||
for (Delimiter delim : delimiters) {
|
for (Delimiter delim : delimiters) {
|
||||||
@ -165,7 +190,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
*
|
*
|
||||||
* @return -1: 不是7E帧; 0: 是7E帧但数据不完整; >0: 帧长度(数据完整)
|
* @return -1: 不是7E帧; 0: 是7E帧但数据不完整; >0: 帧长度(数据完整)
|
||||||
*/
|
*/
|
||||||
private int check7EFrame(ByteBuf buffer) {
|
private int check7EFrame(ByteBuf buffer, String deviceId) {
|
||||||
int readable = buffer.readableBytes();
|
int readable = buffer.readableBytes();
|
||||||
if (readable < 2) {
|
if (readable < 2) {
|
||||||
return -1;
|
return -1;
|
||||||
@ -190,18 +215,18 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
|
|
||||||
// 检查数据是否足够
|
// 检查数据是否足够
|
||||||
if (readable < frameLength) {
|
if (readable < frameLength) {
|
||||||
log.debug("7E帧数据不完整,需要{}字节,当前{}字节,等待更多数据", frameLength, readable);
|
log.debug("[{}] 7E帧数据不完整,需要{}字节,当前{}字节,等待更多数据", deviceId, frameLength, readable);
|
||||||
return 0; // 是7E帧但数据不完整
|
return 0; // 是7E帧但数据不完整
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证帧尾是否为7E
|
// 验证帧尾是否为7E
|
||||||
int lastByte = buffer.getByte(readerIndex + frameLength - 1) & 0xFF;
|
int lastByte = buffer.getByte(readerIndex + frameLength - 1) & 0xFF;
|
||||||
if (lastByte != 0x7E) {
|
if (lastByte != 0x7E) {
|
||||||
log.warn("7E帧格式错误,帧尾不是7E,实际值: {}", lastByte);
|
log.warn("[{}] 7E帧格式错误,帧尾不是7E,实际值: {}", deviceId, lastByte);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("成功检测7E帧,类型={}, 长度={}", secondByte == 0x80 ? "注册包" : "心跳包", frameLength);
|
log.debug("[{}] 成功检测7E帧,类型={}, 长度={}", deviceId, secondByte == 0x80 ? "注册包" : "心跳包", frameLength);
|
||||||
return frameLength;
|
return frameLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +234,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
* 检测Modbus RTU帧格式
|
* 检测Modbus RTU帧格式
|
||||||
* @return -1: 不是Modbus帧; 0: 是Modbus帧但数据不完整; >0: 帧长度(数据完整)
|
* @return -1: 不是Modbus帧; 0: 是Modbus帧但数据不完整; >0: 帧长度(数据完整)
|
||||||
*/
|
*/
|
||||||
private int checkModbusRtu(ByteBuf buffer) {
|
private int checkModbusRtu(ByteBuf buffer, String deviceId) {
|
||||||
int readable = buffer.readableBytes();
|
int readable = buffer.readableBytes();
|
||||||
if (readable < 2) {
|
if (readable < 2) {
|
||||||
return -1;
|
return -1;
|
||||||
@ -224,13 +249,16 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查设备地址(1-247)
|
// 检查设备地址:标准1-247,另外允许0x00(广播)和0xFF(常用扩展地址)
|
||||||
if (deviceAddr < 1 || deviceAddr > 247) {
|
boolean validAddr = (deviceAddr >= 1 && deviceAddr <= 247)
|
||||||
|
|| deviceAddr == 0x00
|
||||||
|
|| deviceAddr == 0xFF;
|
||||||
|
if (!validAddr) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modbus RTU:为了避免把请求帧(常见固定8字节)误当响应帧(第3字节是byteCount)导致截断,
|
// Modbus RTU:为了避免把请求帧(常见固定8字节)误当响应帧(第3字节是byteCount)导致截断,
|
||||||
// 这里采用“候选长度 + CRC校验”方式确定帧边界,兼容:请求/回显/响应/异常响应。
|
// 这里采用"候选长度 + CRC校验"方式确定帧边界,兼容:请求/回显/响应/异常响应。
|
||||||
|
|
||||||
// 异常响应:地址(1) + 功能码(1|0x80) + 异常码(1) + CRC(2) = 5字节
|
// 异常响应:地址(1) + 功能码(1|0x80) + 异常码(1) + CRC(2) = 5字节
|
||||||
if ((functionCode & 0x80) != 0) {
|
if ((functionCode & 0x80) != 0) {
|
||||||
@ -238,7 +266,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
if (readable < len) {
|
if (readable < len) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return isValidModbusCrc(buffer, readerIndex, len) ? len : -1;
|
return isValidModbusCrc(buffer, readerIndex, len, deviceId) ? len : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 功能码05/06:请求与响应均为8字节(地址 + 功能码 + 地址(2) + 值(2) + CRC(2))
|
// 功能码05/06:请求与响应均为8字节(地址 + 功能码 + 地址(2) + 值(2) + CRC(2))
|
||||||
@ -247,7 +275,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
if (readable < len) {
|
if (readable < len) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return isValidModbusCrc(buffer, readerIndex, len) ? len : -1;
|
return isValidModbusCrc(buffer, readerIndex, len, deviceId) ? len : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 功能码01/02/03/04:
|
// 功能码01/02/03/04:
|
||||||
@ -256,7 +284,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
if (functionCode >= 0x01 && functionCode <= 0x04) {
|
if (functionCode >= 0x01 && functionCode <= 0x04) {
|
||||||
// 先尝试8字节(避免地址高字节=0x00 被误当 byteCount=0 导致5字节截断)
|
// 先尝试8字节(避免地址高字节=0x00 被误当 byteCount=0 导致5字节截断)
|
||||||
int reqLen = 8;
|
int reqLen = 8;
|
||||||
if (readable >= reqLen && isValidModbusCrc(buffer, readerIndex, reqLen)) {
|
if (readable >= reqLen && isValidModbusCrc(buffer, readerIndex, reqLen, deviceId)) {
|
||||||
return reqLen;
|
return reqLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,13 +293,21 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
}
|
}
|
||||||
int byteCount = buffer.getByte(readerIndex + 2) & 0xFF;
|
int byteCount = buffer.getByte(readerIndex + 2) & 0xFF;
|
||||||
int respLen = 3 + byteCount + 2;
|
int respLen = 3 + byteCount + 2;
|
||||||
|
log.debug("[{}] Modbus功能码{}: byteCount={}, 预期帧长={}, 可读={}",
|
||||||
|
deviceId, String.format("%02X", functionCode), byteCount, respLen, readable);
|
||||||
if (respLen > maxFrameLength) {
|
if (respLen > maxFrameLength) {
|
||||||
|
log.warn("[{}] Modbus帧长度{}超过最大限制{}", deviceId, respLen, maxFrameLength);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (readable < respLen) {
|
if (readable < respLen) {
|
||||||
|
log.debug("[{}] Modbus帧数据不完整,需要{}字节,当前{}字节", deviceId, respLen, readable);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return isValidModbusCrc(buffer, readerIndex, respLen) ? respLen : -1;
|
boolean crcValid = isValidModbusCrc(buffer, readerIndex, respLen, deviceId);
|
||||||
|
if (!crcValid) {
|
||||||
|
log.debug("[{}] Modbus CRC校验失败,帧长={}", deviceId, respLen);
|
||||||
|
}
|
||||||
|
return crcValid ? respLen : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 功能码0F/10:
|
// 功能码0F/10:
|
||||||
@ -279,7 +315,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
// - 请求可变:地址(1)+功能码(1)+起始地址(2)+数量(2)+byteCount(1)+values(N)+CRC(2) => 9+N
|
// - 请求可变:地址(1)+功能码(1)+起始地址(2)+数量(2)+byteCount(1)+values(N)+CRC(2) => 9+N
|
||||||
if (functionCode == 0x0F || functionCode == 0x10) {
|
if (functionCode == 0x0F || functionCode == 0x10) {
|
||||||
int respLen = 8;
|
int respLen = 8;
|
||||||
if (readable >= respLen && isValidModbusCrc(buffer, readerIndex, respLen)) {
|
if (readable >= respLen && isValidModbusCrc(buffer, readerIndex, respLen, deviceId)) {
|
||||||
return respLen;
|
return respLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,7 +330,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
if (readable < reqLen) {
|
if (readable < reqLen) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return isValidModbusCrc(buffer, readerIndex, reqLen) ? reqLen : -1;
|
return isValidModbusCrc(buffer, readerIndex, reqLen, deviceId) ? reqLen : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1; // 不是已知的Modbus功能码
|
return -1; // 不是已知的Modbus功能码
|
||||||
@ -305,8 +341,9 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
* @param buffer ByteBuf
|
* @param buffer ByteBuf
|
||||||
* @param offset 起始偏移(通常为 readerIndex)
|
* @param offset 起始偏移(通常为 readerIndex)
|
||||||
* @param len 帧长度(包含CRC2字节)
|
* @param len 帧长度(包含CRC2字节)
|
||||||
|
* @param deviceId 设备标识(用于日志)
|
||||||
*/
|
*/
|
||||||
private boolean isValidModbusCrc(ByteBuf buffer, int offset, int len) {
|
private boolean isValidModbusCrc(ByteBuf buffer, int offset, int len, String deviceId) {
|
||||||
if (len < 5) {
|
if (len < 5) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -315,7 +352,13 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
|
|||||||
byte[] crc = CRC16Utils.getCrc16Byte(data); // [low, high]
|
byte[] crc = CRC16Utils.getCrc16Byte(data); // [low, high]
|
||||||
int crcLo = buffer.getByte(offset + len - 2) & 0xFF;
|
int crcLo = buffer.getByte(offset + len - 2) & 0xFF;
|
||||||
int crcHi = buffer.getByte(offset + len - 1) & 0xFF;
|
int crcHi = buffer.getByte(offset + len - 1) & 0xFF;
|
||||||
return ((crc[0] & 0xFF) == crcLo) && ((crc[1] & 0xFF) == crcHi);
|
boolean valid = ((crc[0] & 0xFF) == crcLo) && ((crc[1] & 0xFF) == crcHi);
|
||||||
|
if (!valid && log.isDebugEnabled()) {
|
||||||
|
log.debug("[{}] CRC校验: 计算值={}{}, 实际值={}{}", deviceId,
|
||||||
|
String.format("%02X", crc[0] & 0xFF), String.format("%02X", crc[1] & 0xFF),
|
||||||
|
String.format("%02X", crcLo), String.format("%02X", crcHi));
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fail(long frameLength) {
|
private void fail(long frameLength) {
|
||||||
|
|||||||
@ -50,13 +50,6 @@ public class TCPMessageAdapter extends ChannelInboundHandlerAdapter {
|
|||||||
ByteBuf buf = (ByteBuf) msg;
|
ByteBuf buf = (ByteBuf) msg;
|
||||||
/*session存储*/
|
/*session存储*/
|
||||||
Session session = getSession(ctx);
|
Session session = getSession(ctx);
|
||||||
// 临时记录设备原始数据日志
|
|
||||||
// 针对特定设备记录原始数据日志
|
|
||||||
String serialNumber = session.getClientId();
|
|
||||||
ByteBuf copy = buf.duplicate();
|
|
||||||
byte[] source = new byte[buf.writerIndex()];
|
|
||||||
copy.readBytes(source);
|
|
||||||
log.warn("==设备[{}]原始数据:{}", serialNumber, ByteBufUtil.hexDump(source));
|
|
||||||
session.access();
|
session.access();
|
||||||
/*将设备端消息传递给下一个处理器*/
|
/*将设备端消息传递给下一个处理器*/
|
||||||
ctx.fireChannelRead(Packet.of(session, buf));
|
ctx.fireChannelRead(Packet.of(session, buf));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user