# 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 Mono request(Message message, Class responseClass) { String key = requestKey(message, responseClass); // 生成匹配key Mono 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()