当前位置: 首页 > news >正文

第18篇_Client 07|超时、断线、重试和真机验证怎样收口

适合谁收藏

  • 正在让 PLC 主动访问 HTTP 服务的工程师。
  • 需要处理请求构造、响应边界、超时与连接复用的人。
  • 希望把 Client 故障定位到确定状态和错误出口的读者。

本篇位置客户端篇,第 7/7 篇;主系列第 18/28 篇。

现场问题

最危险的处理方式,是 Client 一报错就立即重发。对于读取类请求可能只是重复流量,对于带动作的 POST,则可能让服务端执行两次。

PLC 需要先判断事务在哪一步失败、请求是否可能已经送达、方法是否允许重试,再决定关闭、复位或等待人工处理。

先给结论

恢复流程固定为:锁存错误证据、停止当前读写、关闭失效句柄、清理事务缓冲区、回到可控状态。是否重试由上层策略决定,协议栈不无条件重复业务请求。

读图重点

这张图只压缩本篇的判断路径。读图时先找“连接失败”对应的输入边界,再沿着“对端返回不可解析消息”检查状态怎样推进,最后用“记录原始响应并停止盲重试”确认输出是否已经形成验收证据。

把对象和边界分开

对象或阶段工程职责现场观察点
连接失败请求尚未发送可按次数和退避策略重试
发送中断服务端是否收到不确定保留请求标识并谨慎重试
响应超时请求可能已执行先查询结果或按业务幂等判断
协议错误对端返回不可解析消息记录原始响应并停止盲重试

从协议约束到代码职责

协议约束

恢复流程固定为:锁存错误证据、停止当前读写、关闭失效句柄、清理事务缓冲区、回到可控状态。是否重试由上层策略决定,协议栈不无条件重复业务请求。 这条结论先限定消息什么时候成立,再限定哪个角色可以消费结果。若绕过协议边界直接驱动业务,半包、超时、重复执行和连接残留就会进入应用层。

工程抽象

当前工程暴露超时、NBS 错误、HTTP 错误、最近响应和 ClientMetrics。真机测试应覆盖端口拒绝、服务端延迟、半响应、主动关闭和恢复后的下一次正常请求。

文章只把“自动重试”作为有前提的策略,不把它写成默认最佳实践。工业现场更看重动作唯一性和可追溯性。

  • 连接失败:TCP 尚未建立,请求字节没有离开 PLC。记录目标地址、端口、连接状态和底层错误后,可以按限定次数与退避时间重连。
  • 发送中断:部分字节可能已经到达服务端,不能仅凭 Write 失败判断业务没有执行。必须保留请求标识、已发送长度和失败时刻,再由上层决定是否补偿。
  • 响应超时:请求可能已经完整送达并被处理,只是响应没有在期限内返回。GET 可在策略允许时重试;带动作的 POST 应先查询结果或依赖幂等键,禁止直接重复执行。
  • 协议错误:连接存在但报文不能被 Parser 接受。此时保存原始响应、解析阶段和 HTTP 错误码,关闭当前连接并停止自动重试,避免同一坏报文反复消耗扫描周期。

程序单元

本篇主证据来自FB_HttpClient.st中以E_HttpClientState.iFault为定位点的连续源码。这里不是为了展示语法,而是把协议约束落到确定程序单元:输入先进入结构体或缓冲区,状态机只在本周期处理可确认的部分,长度和结束条件决定能否前进,错误码与指标负责把失败原因带出对象边界。这样一来,“先判断失败阶段,再谈重试。”可以在代码、在线变量和外部报文之间逐项对照,而不是依赖经验猜测。

本篇核心源码片段

下面两段代码来自同一个真实文件FB_HttpClient.st,以E_HttpClientState.iFault为中心连续截取,没有改写变量、删除分支或用伪代码替代。第一段用于确认入口与前置条件,第二段用于确认状态、边界和输出。核对时重点看“请求尚未发送”怎样进入对象,以及“记录原始响应并停止盲重试”怎样证明本次处理已经结束。若两段之间的连续关系无法解释“恢复前必须保留错误和原始报文证据。”,就不能把局部代码截图当成实现证据。

片段一:入口、声明与前置条件

hConnection := hConnection, szSize := 0, pData := ADR(aTxBuf) ); tonWrite( IN := FALSE, PT := GVL_Http.cnWriteTimeout ); M_Reset(); RETURN; END_IF bDone := FALSE; CASE eState OF E_HttpClientState.iDisabled: eState := E_HttpClientState.iIdle; E_HttpClientState.iIdle: bBusy := FALSE; IF rtrigSend.Q THEN M_PrepareRequest(); stMetrics.udiRequestCount := stMetrics.udiRequestCount + 1; IF NOT bRequestQueued THEN eState := E_HttpClientState.iFault; ELSIF (hConnection <> 0) AND bTcpConnected AND NOT stRequest.bConnectionClose THEN bReuseAttempt := TRUE; eState := E_HttpClientState.iSend; ELSE bReuseAttempt := FALSE; eState := E_HttpClientState.iTcpConnect; END_IF END_IF E_HttpClientState.iTcpConnect: bBusy := TRUE; fbTcpClient( xEnable := TRUE, udiTimeOut := udiTimeOut, ipAddr := ipServer, uiPort := uiEffectivePort, eError => eTcpError, hConnection => hConnection ); bTcpConnected := fbTcpClient.xActive AND (hConnection <> 0); IF fbTcpClient.xError THEN eLastNbsError := eTcpError; stMetrics.udiConnectErrorCount := stMetrics.udiConnectErrorCount + 1; M_SetClientError( eError := E_HttpError.iTcpClientFailed, sMessage := 'TCP connect failed' ); eState := E_HttpClientState.iFault; ELSIF bTcpConnected THEN eState := E_HttpClientState.iSend; ELSIF (udiNowMs <> 0) AND ((udiNowMs - udiStateEnterMs) >= udiTimeoutMs) THEN M_SetClientError( eError := E_HttpError.iTimeout, sMessage := 'TCP connect timeout'

这一段先回答对象在什么输入和状态下开始工作。阅读时要核对变量的初值、长度上限和启动条件,不能只看某个布尔量是否变成 TRUE。

片段二:状态推进、边界与输出

); eState := E_HttpClientState.iFault; END_IF E_HttpClientState.iSend: bBusy := TRUE; fbTcpClient( xEnable := TRUE, udiTimeOut := udiTimeOut, ipAddr := ipServer, uiPort := uiEffectivePort, eError => eTcpError, hConnection => hConnection ); M_ServiceWrite(); IF (NOT bWriteBusy) AND (NOT bWriteExecute) THEN eState := E_HttpClientState.iReceive; END_IF E_HttpClientState.iReceive: bBusy := TRUE; fbTcpClient( xEnable := TRUE, udiTimeOut := udiTimeOut, ipAddr := ipServer, uiPort := uiEffectivePort, eError => eTcpError, hConnection => hConnection ); M_ServiceRead(); M_ProcessResponse(); IF (udiNowMs <> 0) AND ((udiNowMs - udiStateEnterMs) >= udiTimeoutMs) THEN M_SetClientError( eError := E_HttpError.iTimeout, sMessage := 'HTTP response timeout' ); eState := E_HttpClientState.iFault; END_IF E_HttpClientState.iDone: bBusy := FALSE; bDone := TRUE; IF stRequest.bConnectionClose OR xCloseConnection THEN fbTcpClient( xEnable := FALSE, ipAddr := ipServer, uiPort := uiEffectivePort, hConnection => hConnection ); bTcpConnected := FALSE; ELSE fbTcpClient( xEnable := TRUE, udiTimeOut := udiTimeOut, ipAddr := ipServer, uiPort := uiEffectivePort, eError => eTcpError, hConnection => hConnection );

第二段继续展示同一连续源码范围。把它与第一段合起来,才能判断输入怎样被锁存、状态何时推进、边界何时满足,以及错误出口是否保留了足够诊断信息。

验证路径

场景操作通过口径
端口拒绝无监听服务连接错误并可恢复
响应延迟超过设定超时Timeout 证据完整
中途断开Body 未收齐时关闭不输出成功
故障后恢复服务恢复再触发请求旧状态不污染新事务

场景 1:端口拒绝

把目标端口改为确定没有服务监听的端口,触发一次 GET。Client 应停在连接阶段,bDone不得出现,连接错误与失败计数只锁存一次;复位后句柄回到无效值,状态重新进入 Idle。这个场景证明请求尚未发送时可以安全重连。

场景 2:响应延迟

让测试服务接收请求后故意延迟响应,延迟时间必须大于 Client 的接收超时。检查状态先进入 Receive,再以iTimeout收口,同时保留已发送请求和等待时长;不能因为没有响应就把本次事务记为未发送,也不能在协议栈内部自动重放 POST。

场景 3:中途断开

让服务端声明一个确定的Content-Length,只发送部分 Body 后主动断开。Parser 必须保持消息未完成,Client 输出传输或协议错误,已有半包不能进入业务响应;关闭并清理后,接收缓冲区长度应回到零。

场景 4:故障后恢复

在完成一次端口拒绝或中途断开后恢复服务,再由上层明确触发一笔新 GET。新事务应建立新连接、重新累计 Tx/Rx,并得到合法状态码与完整 Body;旧错误可供历史诊断,但不得继续拉高bError或污染新响应。

常见误判

  • 任何错误都立即重试,没有判断请求是否可能已经送达服务端。
  • 延长超时掩盖状态机没有推进,最终只是让故障更晚暴露。
  • 故障后直接发下一条请求,没有先关闭句柄和清理接收缓冲区。

这些误判的共同点,是拿一个局部现象替代完整事务。定位时必须回到本篇的输入、状态、边界和输出四个坐标,并用相同输入完成回归。

这一篇你最该记住

  • 先判断失败阶段,再谈重试。
  • POST 超时不能默认安全重发。
  • 恢复前必须保留错误和原始报文证据。

系列导航

  • 系列:CodeSys HTTP 系列教程,第 18/28 篇。
  • 阶段:客户端篇,职责线位置 7/7。
  • 上一篇:第17篇
  • 下一篇:第19篇
  • 发布顺序:基础认知 -> Server -> Client -> 完整源码加更 -> 综合收束。
http://www.cnnetsun.cn/news/4170929.html

相关文章:

  • 嵌入式低代码开发实战:AWFlow图形化框架解析与应用
  • 064、SQL跟踪与性能分析(ST05)
  • 均匀随机相位下正弦信号幅度分布:从概率密度到工程应用
  • AI专利申请怎么写技术交底书
  • Vue+Flask求职推荐系统:Apriori算法优化人岗匹配
  • C++模板分离编译问题解析:从链接错误到模板特化实战
  • 华为杯数学建模竞赛全流程实战指南:从组队到论文的避坑经验
  • PySpark岭回归实战:大数据场景下的线性模型调优与避坑指南
  • 模型路由引擎:应对AI技术奇点的灵活架构与自建指南
  • Python爬虫实战:基于最新技术的招聘信息抓取系统
  • Python 适合做 Web 后端吗?对比 Java、Go,优缺点讲明白
  • Vue+Flask构建毕业生招聘推荐系统实战
  • 武汉市人社局:关于2026年度职称评审工作的重要通知+工作重点
  • Metis:桥接文本与代码记忆,驱动AI智能体自我进化的核心技术
  • SAP ME实施落地指南:从核心概念到生产订单全流程解析
  • 英语五大基础句型+谓语、非谓语和时态
  • L1正则化原理详解:从几何直观到稀疏解的产生机制
  • 基于SpringBoot的智慧教学平台中智能问答系统(源码+文档+讲解视频)
  • S-JEPA中GMM概率映射对编码器表示质量的关键影响
  • DHCP三剑客配置(2)
  • 03-02-线性-List-T-动态数组布局-扩容与操作成本
  • 开源跨平台SSH工具全解析:集成数据库管理、云端同步的远程工作台
  • C++ CRTP模式:从静态多态到表达式模板的编译期优化实践
  • 字典数据结构实战:从算法竞赛题看哈希表的应用与优化
  • 知医邦AI五音闻诊,实现辨音听曲养生
  • 插值与拟合:从数据点到连续模型的数学工具选择与实践
  • 嵌入式IDE变天:开发正在Agent化
  • 投票活动出现异常怎么排查?刷票误判、数据异常、访问卡顿等场景全解
  • 2026毕业生必备:十大AI写作工具评测与求职应用指南
  • SVM实战:从葡萄酒分类看机器学习分类算法原理与应用