输入一个 URL 以后,一次 HTTP 事务经历了什么 第02篇_输入一个 URL 以后一次 HTTP 事务经历了什么[!abstract] 用http://192.168.1.20:8088/api/status跟踪同一笔事务URL 怎样拆成连接参数与消息参数请求怎样到达 PLC响应又怎样回到 Client。适合谁收藏第一次系统理解 HTTP 的 PLC 工程师。已经会调用通信功能块但分不清 TCP 连通与 HTTP 完整事务的人。需要建立后续 Server、Client 学习坐标的读者。[!note] 本篇位置 基础认知第 2/4 篇主系列第 02/28 篇。现场问题在浏览器或 curl 中输入一个 URL看起来只有一次点击。程序内部却必须连续完成地址解析、TCP 连接、请求构造、字节发送、Server 接入、消息解析、资源处理、响应构造、响应接收和事务收尾。任何一步没有完成外部都可能只表现为“没有响应”。如果不知道事务停在哪一步就只能在所有功能块之间来回猜。先给结论一次 HTTP 事务不是一次函数调用而是一条有顺序、有输入输出、有结束条件的链。URL 同时提供 TCP 连接信息和 HTTP 消息信息TCP 通道、HTTP 消息和 PLC 业务沿同一笔事务推进但必须分层观察。读图重点这张图只压缩本篇的判断路径。读图时先找“解析 URL”对应的输入边界再沿着“路由、状态码、Body”检查状态怎样推进最后用“业务结果回到 HTTP 响应”确认输出是否已经形成验收证据。把对象和边界分开对象或阶段工程职责现场观察点解析 URLscheme、host、port、path连接参数和请求参数被分开建立通道Client Connect、Server Listen/Accept双方获得 TCP 连接句柄交换消息Builder、Write、Read、Parser请求和响应跨周期完成处理资源路由、状态码、Body业务结果回到 HTTP 响应一笔事务的十个连续步骤Client 把 URL 拆成 scheme、host、port 和 path。Client 使用 host 与 port 建立 TCP 连接。Builder 用 method、path、Host 和连接策略构造请求。TCP_Write 把请求字节发送出去。Server 从监听器接入连接并绑定独立槽位。槽位跨扫描周期读取Parser 等待完整请求。应用根据目标路径读取或修改资源。Builder 构造带状态码、长度和 Body 的响应。Client 跨周期接收并解析完整响应。双方关闭连接或清理事务后顺序复用连接。这十步不是理论上的“可能过程”而是排障坐标。无法连接时查第 2 步已经连接但没有请求时查第 3 至 6 步返回 400 时查第 6 步返回 404 时查第 7 步Client 一直 Receive 时查第 8 至 9 步第二次请求才出错时查第 10 步。三条证据线证据线开始结束典型观察量控制流Enable、Execute、新连接Done、Closing、Fault状态枚举、超时、复位数据流URL、请求参数、连接句柄状态码、Header、BodyTx/Rx 长度、结构体字段诊断流底层或协议异常错误锁存与恢复NBS error、HTTP error、Metrics三条线都闭合才能宣布事务完成。只看到 Body 正确仍可能遗漏连接泄漏只看到 Done也可能遗漏内容错误。从协议约束到代码职责协议约束一次 HTTP 事务不是一次函数调用而是一条有顺序、有输入输出、有结束条件的链。URL 同时提供 TCP 连接信息和 HTTP 消息信息TCP 通道、HTTP 消息和 PLC 业务沿同一笔事务推进但必须分层观察。 这条结论先限定消息什么时候成立再限定哪个角色可以消费结果。若绕过协议边界直接驱动业务半包、超时、重复执行和连接残留就会进入应用层。工程抽象以该 URL 为例192.168.1.20和8088用于建立 TCP 连接/api/status进入 HTTP 请求行主机与端口还要进入 Host Header。连接成功时Server 只知道有一条字节通道并不知道对方要读取哪个资源。Builder 把结构化请求变成报文Parser 把报文还原成结构化对象。应用层只消费完整请求并把处理结果写入响应对象。这样每个故障都能定位到连接、构造、发送、解析、路由或响应不必用一个 Busy 信号代表整条链。解析 URL工程职责是“scheme、host、port、path”。它不能只停留在命名层面运行时必须能通过“连接参数和请求参数被分开”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。建立通道工程职责是“Client Connect、Server Listen/Accept”。它不能只停留在命名层面运行时必须能通过“双方获得 TCP 连接句柄”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。交换消息工程职责是“Builder、Write、Read、Parser”。它不能只停留在命名层面运行时必须能通过“请求和响应跨周期完成”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。处理资源工程职责是“路由、状态码、Body”。它不能只停留在命名层面运行时必须能通过“业务结果回到 HTTP 响应”观察到输入、状态或结果否则这一层即使有代码也没有形成可验证边界。程序单元本篇主证据来自PLC_PRG.st中以PROGRAM PLC_PRG为定位点的连续源码。这里不是为了展示语法而是把协议约束落到确定程序单元输入先进入结构体或缓冲区状态机只在本周期处理可确认的部分长度和结束条件决定能否前进错误码与指标负责把失败原因带出对象边界。这样一来“一个 URL 会同时驱动 TCP 连接和 HTTP 请求。”可以在代码、在线变量和外部报文之间逐项对照而不是依赖经验猜测。本篇核心源码片段下面两段代码来自同一个真实文件PLC_PRG.st以PROGRAM PLC_PRG为中心连续截取没有改写变量、删除分支或用伪代码替代。第一段用于确认入口与前置条件第二段用于确认状态、边界和输出。核对时重点看“scheme、host、port、path”怎样进入对象以及“业务结果回到 HTTP 响应”怎样证明本次处理已经结束。若两段之间的连续关系无法解释“排障先定位步骤再检查对应对象。”就不能把局部代码截图当成实现证据。片段一入口、声明与前置条件PROGRAM PLC_PRG VAR fbHttpClient : FB_HttpClient; // HTTP Client 主实例。 fbHttpServer : FB_HttpServer; // HTTP Server 主实例。 stClientMetrics : ST_HttpClientMetrics; // Client 指标输出。 stServerMetrics : ST_HttpServerMetrics; // Server 指标输出。 stClientResponse : ST_HttpResponse; // Client 响应输出。 aServerSnapshots : ARRAY[1..GVL_Http.cnMaxClientSlots] OF ST_HttpConnectionSnapshot; // Server 连接快照。 END_VAR // IMPLEMENTATION /// /// 名称 : PLC_PRG /// 功能 : HTTP 示例工程主任务直接完整调用 Client 和 Server 功能块。 /// 库依赖 : CAA Net Base Services /// /// 使用说明 : 1. Client/Server 所有真实网络命令默认关闭由 GVL_HttpRealTest 在线变量触发。 /// : 2. 本主程序完整列出功能块引脚便于 CODESYS/SmartControl 用户按引脚理解用法。 /// // 工程说明本段集中处理状态、边界或诊断避免跨周期残留。 // 约束PLC_PRG 作为示例主入口直接调用 HTTP 功能块用户联调不再依赖辅助 PRG。 GVL_HttpRealTest.udiNowMs : GVL_HttpRealTest.udiNowMs 10; IF GVL_HttpRealTest.bResetResults THEN fbHttpServer.M_Reset(); fbHttpClient.M_Reset(); GVL_HttpRealTest.bClientSend : FALSE; GVL_HttpRealTest.bClientAbort : FALSE; GVL_HttpRealTest.bServerPassLatched : FALSE; GVL_HttpRealTest.bClientPassLatched : FALSE; GVL_HttpRealTest.bOverallPassLatched : FALSE; END_IF fbHttpServer( xEnable : GVL_HttpRealTest.bServerEnable, xReset : GVL_HttpRealTest.bResetResults, xCloseConnection : GVL_HttpRealTest.bServerCloseConnection, bEnable : GVL_HttpRealTest.bServerEnable, sBindIP : GVL_HttpRealTest.sServerBindIP, uiPort : GVL_HttpRealTest.uiServerPort, uiResponseStatusCode : GVL_HttpRealTest.uiServerResponseStatusCode, sResponseBody : GVL_HttpRealTest.sServerResponseBody, sResponseContentType : GVL_HttpRealTest.sServerResponseContentType, sAdditionalHeader : GVL_HttpRealTest.sServerAdditionalHeader, udiNowMs : GVL_HttpRealTest.udiNowMs, xListening GVL_HttpRealTest.bServerListening, xRunning GVL_HttpRealTest.bServerRunning, xBusy GVL_HttpRealTest.bServerBusy, xError GVL_HttpRealTest.bServerError, bListening GVL_HttpRealTest.bServerListening, bRunning GVL_HttpRealTest.bServerRunning, bBusy GVL_HttpRealTest.bServerBusy,这一段先回答对象在什么输入和状态下开始工作。阅读时要核对变量的初值、长度上限和启动条件不能只看某个布尔量是否变成 TRUE。片段二状态推进、边界与输出bError GVL_HttpRealTest.bServerError, diErrorID GVL_HttpRealTest.diServerErrorID, sDiagMsg GVL_HttpRealTest.sServerDiagMsg, eLastNbsError GVL_HttpRealTest.eServerLastNbsError, eState GVL_HttpRealTest.eServerState, eLastError GVL_HttpRealTest.eServerLastError, stMetrics stServerMetrics, uiActiveConnections GVL_HttpRealTest.uiServerActiveConnections, uiLastErrorSlot GVL_HttpRealTest.uiServerLastErrorSlot, uiLastRequestSlot GVL_HttpRealTest.uiServerLastRequestSlot, sRequestTarget GVL_HttpRealTest.sServerLastTarget, sRequestBody GVL_HttpRealTest.sServerLastBody, sRxMessage GVL_HttpRealTest.sServerRxMessage, sTxMessage GVL_HttpRealTest.sServerTxMessage, sLastTarget GVL_HttpRealTest.sServerLastTarget, sLastBody GVL_HttpRealTest.sServerLastBody, hListenHandle GVL_HttpRealTest.hServerListenHandle, aConnectionSnapshots aServerSnapshots ); fbHttpClient( xEnable : GVL_HttpRealTest.bClientEnable, xExecute : GVL_HttpRealTest.bClientSend, xReset : GVL_HttpRealTest.bResetResults, xAbort : GVL_HttpRealTest.bClientAbort, xCloseConnection : GVL_HttpRealTest.bClientCloseConnection, bEnable : GVL_HttpRealTest.bClientEnable, bSend : GVL_HttpRealTest.bClientSend, udiTimeOut : GVL_HttpRealTest.udiClientTimeoutUs, sURL : GVL_HttpRealTest.sClientURL, sServerIP : GVL_HttpRealTest.sClientServerIP, uiPort : GVL_HttpRealTest.uiClientPort, sHost : GVL_HttpRealTest.sClientHost, sPath : GVL_HttpRealTest.sClientPath, eRequestType : GVL_HttpRealTest.eClientMethod, eMethod : GVL_HttpRealTest.eClientMethod, sBody : GVL_HttpRealTest.sClientBody, sContentType : GVL_HttpRealTest.sClientContentType, sAdditionalHeader : GVL_HttpRealTest.sClientAdditionalHeader, udiNowMs : GVL_HttpRealTest.udiNowMs, xActive GVL_HttpRealTest.bClientTcpConnected, xConnected GVL_HttpRealTest.bClientTcpConnected, xBusy GVL_HttpRealTest.bClientBusy, xDone GVL_HttpRealTest.bClientDone, xError GVL_HttpRealTest.bClientError, bTcpConnected GVL_HttpRealTest.bClientTcpConnected, bBusy GVL_HttpRealTest.bClientBusy, bDone GVL_HttpRealTest.bClientDone, bError GVL_HttpRealTest.bClientError, diErrorID GVL_HttpRealTest.diClientErrorID, sDiagMsg GVL_HttpRealTest.sClientDiagMsg, eLastNbsError GVL_HttpRealTest.eClientLastNbsError,第二段继续展示同一连续源码范围。把它与第一段合起来才能判断输入怎样被锁存、状态何时推进、边界何时满足以及错误出口是否保留了足够诊断信息。验证路径场景操作通过口径URL 拆分标出 scheme、host、port、path连接信息与消息信息不混用请求跟踪从 Connect 跟到 Parser Done每一步都有状态或数据证据响应跟踪从路由结果跟到 Client Done状态码、长度和 Body 一致连接收尾短连接或顺序复用事务状态均被清理场景 1URL 拆分执行“标出 scheme、host、port、path”前先清理上一笔事务的完成脉冲、错误锁存和接收残留再记录起始状态与计数。操作后同时观察外部报文、角色状态机和诊断量只有三条证据共同指向“连接信息与消息信息不混用”这一场景才算通过。若只看到外部结果而内部状态未收口应继续检查资源回收若内部 Done 已出现而报文不完整应回到消息边界重新取证。场景 2请求跟踪执行“从 Connect 跟到 Parser Done”前先清理上一笔事务的完成脉冲、错误锁存和接收残留再记录起始状态与计数。操作后同时观察外部报文、角色状态机和诊断量只有三条证据共同指向“每一步都有状态或数据证据”这一场景才算通过。若只看到外部结果而内部状态未收口应继续检查资源回收若内部 Done 已出现而报文不完整应回到消息边界重新取证。场景 3响应跟踪执行“从路由结果跟到 Client Done”前先清理上一笔事务的完成脉冲、错误锁存和接收残留再记录起始状态与计数。操作后同时观察外部报文、角色状态机和诊断量只有三条证据共同指向“状态码、长度和 Body 一致”这一场景才算通过。若只看到外部结果而内部状态未收口应继续检查资源回收若内部 Done 已出现而报文不完整应回到消息边界重新取证。场景 4连接收尾执行“短连接或顺序复用”前先清理上一笔事务的完成脉冲、错误锁存和接收残留再记录起始状态与计数。操作后同时观察外部报文、角色状态机和诊断量只有三条证据共同指向“事务状态均被清理”这一场景才算通过。若只看到外部结果而内部状态未收口应继续检查资源回收若内部 Done 已出现而报文不完整应回到消息边界重新取证。常见误判把完整 URL 原样交给 TCP混淆 Host、端口和请求路径的职责。看到 Connect 成功就跳过请求构造、发送和响应接收。只画数据流不标出事务状态和结束条件现场仍然无法定位卡点。这些误判的共同点是拿一个局部现象替代完整事务。定位时必须回到本篇的输入、状态、边界和输出四个坐标并用相同输入完成回归。这一篇你最该记住一个 URL 会同时驱动 TCP 连接和 HTTP 请求。完整事务必须走完请求与响应两个方向。排障先定位步骤再检查对应对象。系列导航系列CodeSys HTTP 系列教程第 02/28 篇。阶段基础认知职责线位置 2/4。上一篇第01篇下一篇第03篇发布顺序基础认知 - Server - Client - 完整源码加更 - 综合收束。

本月热点