第06篇_Server 02|监听端口以后,PLC 为什么还没有收到请求 适合谁收藏正在实现 PLC 端 HTTP Server 的工程师。正在排查监听、多连接、请求边界或响应发送问题的人。需要建立 Server 真机验收清单的读者。本篇位置服务器篇第 2/7 篇主系列第 06/28 篇。现场问题调试 PLC Server 时经常先看到端口 8088 已经可以连接于是开始在业务代码里查为什么没有路径和 Body。问题通常不在业务层而在监听句柄之后还有一条没有走完的链。NBS.TCP_Server负责监听NBS.TCP_Connection负责接入。只有接入句柄绑定到一个 HTTP 连接槽位并且该槽位持续执行 Read协议层才可能获得完整请求。先给结论Server 的第一条验收线不是“端口能连”而是“监听句柄有效、连接槽位激活、请求进入解析器、响应可以返回”。四个状态缺一个都不能宣布跑通。读图重点这张图只压缩本篇的判断路径。读图时先找“Disabled”对应的输入边界再沿着“接入并服务所有槽位”检查状态怎样推进最后用“聚合请求和错误”确认输出是否已经形成验收证据。把对象和边界分开对象或阶段工程职责现场观察点Disabled未使能或已复位监听句柄必须为 0Init清理历史错误与句柄准备进入 ListenListen周期调用 TCP_Server等待有效 hServerRunning接入并服务所有槽位聚合请求和错误从协议约束到代码职责协议约束Server 的第一条验收线不是“端口能连”而是“监听句柄有效、连接槽位激活、请求进入解析器、响应可以返回”。四个状态缺一个都不能宣布跑通。 这条结论先限定消息什么时候成立再限定哪个角色可以消费结果。若绕过协议边界直接驱动业务半包、超时、重复执行和连接残留就会进入应用层。工程抽象Server 外层状态机只管理监听和槽位调度。单连接收发交给FB_HttpServerConnection这避免一个连接的半包或错误阻塞其他连接。监听状态必须对外暴露xListening、bListening和hListenHandle。现场如果只给一个bError就无法判断故障发生在监听、接入还是 HTTP 解析阶段。Disabled工程职责是“未使能或已复位”。它不能只停留在命名层面运行时必须能通过“监听句柄必须为 0”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。Init工程职责是“清理历史错误与句柄”。它不能只停留在命名层面运行时必须能通过“准备进入 Listen”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。Listen工程职责是“周期调用 TCP_Server”。它不能只停留在命名层面运行时必须能通过“等待有效 hServer”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。Running工程职责是“接入并服务所有槽位”。它不能只停留在命名层面运行时必须能通过“聚合请求和错误”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。程序单元本篇主证据来自FB_HttpServer.st中以CASE eState OF为定位点的连续源码。这里不是为了展示语法而是把协议约束落到确定程序单元输入先进入结构体或缓冲区状态机只在本周期处理可确认的部分长度和结束条件决定能否前进错误码与指标负责把失败原因带出对象边界。这样一来“监听、接入和 HTTP 请求是三个不同事件。”可以在代码、在线变量和外部报文之间逐项对照而不是依赖经验猜测。本篇核心源码片段下面两段代码来自同一个真实文件FB_HttpServer.st以CASE eState OF为中心连续截取没有改写变量、删除分支或用伪代码替代。第一段用于确认入口与前置条件第二段用于确认状态、边界和输出。核对时重点看“未使能或已复位”怎样进入对象以及“聚合请求和错误”怎样证明本次处理已经结束。若两段之间的连续关系无法解释“端口检查必须继续追到请求和响应证据。”就不能把局部代码截图当成实现证据。片段一入口、声明与前置条件udiNowMs : udiNowMs ); aConnectionSnapshots[uiSlotIndex] : aConnections[uiSlotIndex].stConnection; END_FOR M_Reset(); RETURN; END_IF CASE eState OF E_HttpServerState.iDisabled: eState : E_HttpServerState.iInit; E_HttpServerState.iInit: hServer : 0; hListenHandle : 0; bError : FALSE; eLastError : E_HttpError.iNoError; sDiagMsg : ; eState : E_HttpServerState.iListen; E_HttpServerState.iListen, E_HttpServerState.iRunning: fbServer( xEnable : TRUE, ipAddr : ipBindAddr, uiPort : uiPort, eError eTcpError ); hServer : fbServer.hServer; hListenHandle : hServer; IF fbServer.xError THEN eLastNbsError : eTcpError; M_SetServerError( eError : E_HttpError.iTcpServerFailed, sMessage : TCP server listen failed ); eState : E_HttpServerState.iFault; ELSIF hServer 0 THEN eState : E_HttpServerState.iRunning;这一段先回答对象在什么输入和状态下开始工作。阅读时要核对变量的初值、长度上限和启动条件不能只看某个布尔量是否变成 TRUE。片段二状态推进、边界与输出M_AcceptNewConnection(); M_ServiceConnections(); END_IF E_HttpServerState.iFault: fbServer( xEnable : FALSE, ipAddr : ipBindAddr, uiPort : uiPort ); ELSE eState : E_HttpServerState.iFault; END_CASE M_UpdateServerOutputs(); // METHOD M_AcceptNewConnection /// /// 名称 : M_AcceptNewConnection /// 功能 : 周期调用所有 NBS.TCP_Connection 接入槽位。 /// 说明 : 所有槽位每周期都调用符合 NBS 电平触发语义。 /// {attribute hide_all_locals} METHOD PRIVATE M_AcceptNewConnection // IMPLEMENTATION // 工程说明本段集中处理状态、边界或诊断避免跨周期残留。 // 边界说明执行前后保持输出和错误码可被在线诊断追踪。 bAcceptError : FALSE; FOR uiSlotIndex : 1 TO GVL_Http.cnMaxClientSlots DO aTcpAccept[uiSlotIndex]( xEnable : hServer 0, hServer : hServer ); IF aTcpAccept[uiSlotIndex].xActive THEN IF (NOT aConnections[uiSlotIndex].bActive) OR (aConnections[uiSlotIndex].stConnection.hConnection aTcpAccept[uiSlotIndex].hConnection) THEN // 原因接入阶段只绑定 NBS 句柄读写统一留给 M_ServiceConnections // 避免新连接在同一扫描周期被 TCP_Read 调用两次导致大 body 半包场景被误判为接收错误。 aConnections[uiSlotIndex].M_Attach(第二段继续展示同一连续源码范围。把它与第一段合起来才能判断输入怎样被锁存、状态何时推进、边界何时满足以及错误出口是否保留了足够诊断信息。验证路径场景操作通过口径监听hListenHandle 非 0状态为 Running证明端口由 PLC 持有接入AcceptedConnections 递增证明外部连接进入槽位请求uiLastRequestSlot 和目标路径更新证明 HTTP 请求完成响应外部工具获得合法响应证明事务闭环场景 1监听启动 Server 后检查hListenHandle非 0、状态为 Running并用外部工具确认目标端口可以建立 TCP 连接。这一步只证明端口由 PLC 持有不代表连接已经进入 HTTP 连接槽更不代表请求已经解析完成。场景 2接入保持连接但暂不发送完整请求观察AcceptedConnections、活动连接数和槽位快照。计数递增且某个槽位进入接收态才说明 Accept 结果已经交给连接实例。如果端口能连但这些量不变化应查接入调度不要先改 Parser。场景 3请求发送一条完整请求后uiLastRequestSlot应指向实际处理槽位最近目标路径应与请求行一致接收缓冲的完成边界应由 Parser 给出。只有这三项同时成立才可以把请求交给路由仅看到 Read 返回字节数还不算收到 HTTP 请求。场景 4响应最后核对外部工具收到的状态行、Header 和 Body并观察发送状态退出、连接按策略关闭或回到可复用态。外部响应正确但槽位一直占用说明事务只完成了报文发送没有完成资源回收这种状态不能算 Server 跑通。常见误判看到 hListenHandle 非零就宣布 Server 已经收到 HTTP 请求。监听失败、接入失败和协议解析失败只共用一个模糊错误码。新连接接入后没有持续服务连接槽句柄存在但请求永远不推进。这些误判的共同点是拿一个局部现象替代完整事务。定位时必须回到本篇的输入、状态、边界和输出四个坐标并用相同输入完成回归。这一篇你最该记住监听、接入和 HTTP 请求是三个不同事件。Server 外层只做生命周期和调度。端口检查必须继续追到请求和响应证据。系列导航系列CodeSys HTTP 系列教程第 06/28 篇。阶段服务器篇职责线位置 2/7。上一篇第05篇下一篇第07篇发布顺序基础认知 - Server - Client - 完整源码加更 - 综合收束。