fastbee/TCP通信流程说明.md
2025-12-30 08:02:52 +08:00

186 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TCP通信流程详解
## 核心问题解答
**问题:Message对象是设备直接回复的,还是底层框架封装的?**
**答案:Message对象是底层框架封装的,不是设备直接回复的。**
设备只发送原始的字节数据(ByteBuf),框架负责:
1. 接收设备的原始字节数据
2. 通过协议解码器解析为Message对象
3. 匹配请求和响应
4. 将响应传递给等待的request
## 完整通信流程
### 1. 平台下发指令(requestR调用)
```
PowerOnTriggerTaskExecutor.execute()
↓
MessageManager.requestR(serialNumber, msg, Message.class)
↓
Session.request(request, Message.class)
├─ 生成requestKey(基于Message类型和serNo)
├─ 订阅等待响应(subscribe(key))
└─ 发送指令到设备(channel.writeAndFlush())
```
**关键代码:**
```java
// Session.request()方法
public <T> Mono<T> request(Message message, Class<T> responseClass) {
String key = requestKey(message, responseClass); // 生成匹配key
Mono<T> subscribe = this.subscribe(key); // 订阅等待响应
ChannelFuture channelFuture = channel.writeAndFlush(Packet.of(this, message));
return Mono.create(...).then(subscribe); // 等待设备回复
}
```
### 2. 设备发送原始数据
设备通过TCP连接发送**原始字节数据**(ByteBuf),例如:
- Modbus RTU协议:`7E 01 03 00 00 00 02 C4 0B 7E`
- 设备不知道Message对象,只发送协议规定的字节流
### 3. 框架接收和处理(Netty Handler链)
```
TCPMessageAdapter.channelRead()
↓ 接收原始ByteBuf
MessageDecoderWrapper.channelRead()
↓ 协议解码:ByteBuf → DeviceReport(Message)
DispatcherHandler.channelRead()
↓ 消息分发处理
```
**关键代码:**
```java
// TCPMessageAdapter - 步骤1:接收原始数据
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg; // 设备发送的原始字节
Session session = getSession(ctx);
ctx.fireChannelRead(Packet.of(session, buf)); // 传递给下一个处理器
}
// MessageDecoderWrapper - 步骤2:协议解码
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Packet packet = (Packet) msg;
ByteBuf input = packet.take();
DeviceReport message = decoder.decode(input, packet.session.getClientId());
// 协议解码器将ByteBuf解析为DeviceReport对象
ctx.fireChannelRead(packet.replace(message));
}
// DispatcherHandler - 步骤3:消息分发
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Packet packet = (Packet) msg;
Message request = packet.message; // 已经是Message对象了
// 处理消息...
}
```
### 4. 协议解码器的工作
协议解码器(如ModbusProtocol)负责:
1. 解析原始字节数据
2. 识别是否是设备回复(通过协议特征,如Modbus功能码)
3. 设置isReply标志
4. 封装为DeviceReport对象
**关键代码:**
```java
// ModbusProtocol.decode()
case Write06:
case Write05:
// 如果返回06编码,说明是设备回复
report.setIsReply(true); // 标记为回复消息
report.setStatus(FunctionReplyStatus.SUCCESS);
return report;
```
### 5. 响应匹配机制(关键!)
**问题:设备回复如何匹配到等待的request?**
**答案:通过Session的requestKey和responseKey机制**
```java
// Session.request() - 发送请求时
private static String requestKey(Message request, Class responseClass) {
String className = responseClass.getName();
if (Response.class.isAssignableFrom(responseClass)) {
String serNo = request.getSerNo(); // 使用序列号
return className + '.' + serNo;
}
return className; // 普通Message使用类名
}
// Session.response() - 接收响应时
private static String responseKey(Object response) {
String className = response.getClass().getName();
if (response instanceof Response) {
int serialNo = ((Response) response).getResponseSerialNo();
return className + '.' + serialNo;
}
return className;
}
```
**匹配流程:**
1. 发送请求时,生成requestKey并订阅
2. 设备回复后,协议解码为Message对象
3. **需要调用Session.response(message)**来匹配
4. 如果key匹配,Mono会收到响应,requestR返回成功
### 6. 当前代码的问题(重要发现!)
**重要发现:在当前代码中,Session.response()方法没有被调用!**
这意味着:
- 设备回复的Message对象被解码后,**没有被匹配到等待的request**
- requestR()的Mono会一直等待,直到超时
- **这就是为什么你的代码中,即使设备回复了,requestR()也可能返回超时**
**实际流程:**
1. requestR()发送请求,订阅等待响应
2. 设备回复原始字节数据
3. 协议解码器解码为DeviceReport对象,设置isReply=true
4. **但是,DispatcherHandler将消息发送到MQ,而不是调用Session.response()**
5. requestR()的Mono一直等待,200ms后超时
**关键代码:**
```java
// DispatcherHandler.handleReport() - 设备回复被发送到MQ
private void handleReport(Message request) {
DeviceReport report = (DeviceReport) request;
DeviceReportBo reportBo = new DeviceReportBo();
// ...
reportBo.setIsReply(report.getIsReply()); // 标记为回复
MessageProducer.sendPublishMsg(reportBo); // 发送到MQ,而不是Session.response()
}
```
**这意味着:**
- requestR()机制在当前实现中**可能无法正确工作**
- 设备回复通过MQ异步处理,而不是同步匹配到request
- 你的代码中检查AjaxResult的code和data是正确的方向
- 但需要确认:如果设备回复了,requestR()是否能正确返回?
## 总结
1. **设备只发送原始字节数据**,不知道Message对象
2. **框架负责解码**:ByteBuf → Message对象
3. **协议解码器识别回复**:通过协议特征(如Modbus功能码)设置isReply标志
4. **响应匹配机制**:通过requestKey/responseKey匹配请求和响应
5. **关键问题**:需要确认Session.response()是否被正确调用
## 建议
对于上电触发任务,如果Session.response()没有被调用,那么:
- requestR()可能无法正确识别设备回复
- 需要检查是否有其他地方调用了Session.response()
- 或者需要修改代码,在协议解码后主动调用Session.response()