Repository navigation
🐛 修复 MC 协议残帧误判导致 TCP 流错位与半开连接无法自愈的缺陷 - #3
Merged
Merged
Conversation
## 根因 现场日志 “ReadErrorException: Not enough data read from PLC. Expected at least 4 bytes, but got 0 bytes” 主要不是掉线,而是收发/解析 链路的几处缺陷叠加,导致 TCP 流错位(desync)且坏连接不失效: 1. ExecuteAsync 的读循环用「单次读到 < 缓冲区长度就 break」判断消息边界。 TCP 是字节流,一帧 MC 响应可能被拆成多个分段到达,于是会拿到残帧。 2. MitsubishiHelper.IsIncorrectResponse 把「长度不足 minLength」误判为 「正确」(return false)放行,残帧因此被当作有效响应,不再重试。 3. McCommand.SetResponse 在帧短于帧头时直接 break,静默产出空 Response, 最终在 ReadWordsAsync 变成 “got 0 bytes”。 4. 读到 0 字节(对端关闭/EOF)或传输异常时未失效内部连接,CheckConnection() 仍返回 true,OpenAsync/TryReconnectAsync 开头的 `if (CheckConnection()) return;` 短路而不重连,复用坏 socket。 残帧只读走半截,socket 缓冲区残留的脏字节让之后每次读取都错位,故障持续; 只有 ProbeConnectionAsync 因 finally 里 Disconnect() 销毁并重建 socket 才恢复。 这正是「一旦发生、重启一直报,只有测试连接后才恢复」的原因。 ## 修复 1. 新增 McFrameReader:按协议帧结构读满整帧——先按定长读满帧头 (MC1E=2 / MC3E=11 / MC4E=15),再由帧头「响应数据长度」字段算出剩余字节 并用 ReadExactly 读够整帧;MC1E 响应头无长度字段,按请求推算数据长度。 读到 0 字节即判定对端关闭(EOF)→ 抛 ConnectionException;取消/超时抛 OperationCanceledException,二者严格区分,取消不当断线。 ExecuteAsync 改用该读帧逻辑,彻底消除分段导致的残帧与流错位。 2. 修正 IsIncorrectResponse:长度不足帧头判为「不正确」(return true), 不再把残帧当正确响应放行。 3. SetResponse:帧长不足帧头时抛出明确的 ReadErrorException,不再静默返回空数据。 4. 事务出错即失效连接:ExecuteAsync 在 EOF/传输异常时 InvalidateConnection(), 使 CheckConnection() 随后返回 false,下次操作重连,避免脏字节错位蔓延。 并为 IsSocketAlive 的 Poll + Available 半开检测补充说明性注释。 三种帧(MC1E/MC3E/MC4E)均覆盖读满整帧逻辑。对外异常语义保持不变 (读写路径仍以 ReadErrorException/WriteErrorException/ConnectionException 呈现)。 版本号升至 1.1.1(修订版)。 ## 测试 新增 McFrameReaderTest:验证 MC3E/MC4E/MC1E 响应在逐字节等极端 TCP 分段下 仍能正确拼接为完整帧、数据段中途 EOF 判为断线、取消不误判为断线。 40 通过 / 28 跳过(无实机)/ 0 失败;dotnet build 四目标框架 0 警告 0 错误。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request 描述
提交前检查
变更类型
相关 Issue
目的
问题描述:
上层压力采集服务轮询时偶发报错,且一旦发生,重新启动会持续报同一个错,只有先执行
ProbeConnectionAsync(测试连接)后才能恢复。核心异常:深挖
McProtocolClient收发/解析链路后,确认这主要不是掉线,而是几处缺陷叠加导致 TCP 流错位(desync) 且坏连接不失效:ExecuteAsync读循环用「单次读到 < 缓冲区长度就 break」判断消息边界。TCP 是字节流,一帧 MC 响应可能被拆成多个分段到达,于是拿到残帧。MitsubishiHelper.IsIncorrectResponse把「长度不足 minLength」误判为「正确」(return false)放行,残帧被当作有效响应,不再重试。McCommand.SetResponse在帧短于帧头时直接break,静默产出空 Response,最终在ReadWordsAsync变成 “got 0 bytes”。CheckConnection()仍返回 true,OpenAsync/TryReconnectAsync开头的if (CheckConnection()) return;短路而不重连,复用坏 socket。残帧只读走半截,socket 缓冲区残留的脏字节让之后每次读取都错位,故障持续;只有
ProbeConnectionAsync因finally里Disconnect()销毁并重建 socket 才恢复——这正是「一旦发生、重启一直报,只有测试连接后才恢复」的原因。本次修复:
McFrameReader:按协议帧结构读满整帧——先按定长读满帧头(MC1E=2 / MC3E=11 / MC4E=15),再由帧头「响应数据长度」字段算出剩余字节并用ReadExactly读够整帧;MC1E 响应头无长度字段,按请求推算数据长度。读到 0 字节即判定对端关闭(EOF)→ 抛ConnectionException;取消/超时抛OperationCanceledException,二者严格区分,取消不当断线。ExecuteAsync改用该读帧逻辑,彻底消除分段导致的残帧与流错位。IsIncorrectResponse:长度不足帧头判为「不正确」,不再把残帧当正确响应放行。SetResponse:帧长不足帧头时抛出明确的ReadErrorException,不再静默返回空数据。ExecuteAsync在 EOF/传输异常时InvalidateConnection(),使CheckConnection()随后返回 false,下次操作重连,避免脏字节错位蔓延;并为IsSocketAlive的Poll + Available半开检测补充说明性注释。预期影响:
ConnectAsync/TryReconnectAsync会先断开再重建,无需再手动ProbeConnectionAsync。ReadErrorException/WriteErrorException/ConnectionException呈现)。评估
性能影响:
Rent(256)缓冲区,改为按帧精确分配。兼容性问题:
额外信息
tests/McProtocol/McFrameReaderTest.cs:验证 MC3E/MC4E/MC1E 响应在逐字节等极端 TCP 分段下仍能正确拼接为完整帧、数据段中途 EOF 判为断线、取消不误判为断线。dotnet build四个目标框架(net10.0 / net9.0 / net8.0 / net481)0 警告 0 错误。mas-pressure-collector的PressureAcquisitionService侧已配套做了自动重连(且重连前先强制Disconnect()再TryReconnectAsync);本仓库这几处是从底层根治。图片/截图
无(本次无 UI 变更)
Copyright (c) MAS(厦门威光) Corporation. All rights reserved.