ControlRookie
返回文章

第05篇_Server 01|先跑通最小请求:200 OK 和 pong

先建立一条最小可运行 Server 闭环:PLC 监听 8088,外部请求 `/api/ping`,返回带正确状态行、长度和关闭策略的 `pong`。

先建立一条最小可运行 Server 闭环:PLC 监听 8088,外部请求 /api/ping,返回带正确状态行、长度和关闭策略的 pong。

适合谁收藏

  • 正在实现 PLC 端 HTTP Server 的工程师。
  • 正在排查监听、多连接、请求边界或响应发送问题的人。
  • 需要建立 Server 真机验收清单的读者。

服务器篇,第 1/7 篇;主系列第 05/28 篇。

现场问题

如果一开始就同时调试多连接、POST、chunked 和 keep-alive,任何失败都可能来自十几个位置。更稳妥的入口,是固定一个端口、一条 GET、一个短 Body 和短连接,把监听、接入、解析、路由、构造、发送和关闭全部走通。

/api/ping 不承担业务含义,它只回答一个问题:这套 PLC HTTP Server 能否完成一笔合法事务。

先给结论

最小 Server 验收不是端口可连接,而是 curl 收到 HTTP/1.1 200 OK、正确的 Content-Length 和 pong,同时 PLC 在线变量显示监听、接入、请求、响应和连接回收依次完成。

Server 01|先跑通最小请求:200 OK 和 pong
Server 01|先跑通最小请求:200 OK 和 pong

读图重点

这张图只压缩本篇的判断路径。读图时先找“Enable”对应的输入边界,再沿着“首轮采用短连接”检查状态怎样推进,最后用“响应后槽位可回收”确认输出是否已经形成验收证据。

把对象和边界分开

对象或阶段工程职责现场观察点
Enable启动 Server 并绑定端口Listening 与句柄有效
GET /api/ping最小无 Body 请求目标路径解析正确
200 + pong最小合法响应状态码、长度和 Body 一致
Connection: close首轮采用短连接响应后槽位可回收

从协议约束到代码职责

协议约束

最小 Server 验收不是端口可连接,而是 curl 收到 HTTP/1.1 200 OK、正确的 Content-Length 和 pong,同时 PLC 在线变量显示监听、接入、请求、响应和连接回收依次完成。 这条结论先限定消息什么时候成立,再限定哪个角色可以消费结果。若绕过协议边界直接驱动业务,半包、超时、重复执行和连接残留就会进入应用层。

工程抽象

最小闭环故意减少变量:不带请求 Body,不复用连接,不依赖 JSON,也不接入真实设备动作。它先证明协议栈的骨架成立,再逐步增加连接槽、消息边界和业务路由。

外部结果与 PLC 证据必须对应同一笔请求。只看 curl 结果无法发现槽位泄漏,只看在线变量也无法证明对端收到了合法响应。

  • Enable:工程职责是“启动 Server 并绑定端口”。它不能只停留在命名层面,运行时必须能通过“Listening 与句柄有效”观察到输入、状态或结果;否则这一层即使有代码,也没有形成可验证边界。
  • GET /api/ping:工程职责是“最小无 Body 请求”。它不能只停留在命名层面,运行时必须能通过“目标路径解析正确”观察到输入、状态或结果;否则这一层即使有代码,也没有形成可验证边界。
  • 200 + pong:工程职责是“最小合法响应”。它不能只停留在命名层面,运行时必须能通过“状态码、长度和 Body 一致”观察到输入、状态或结果;否则这一层即使有代码,也没有形成可验证边界。
  • Connection: close:工程职责是“首轮采用短连接”。它不能只停留在命名层面,运行时必须能通过“响应后槽位可回收”观察到输入、状态或结果;否则这一层即使有代码,也没有形成可验证边界。

程序单元

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

本篇核心源码片段

下面两段代码来自同一个真实文件 PLC_PRG.st,以 fbHttpServer( 为中心连续截取,没有改写变量、删除分支或用伪代码替代。第一段用于确认入口与前置条件,第二段用于确认状态、边界和输出。核对时重点看“启动 Server 并绑定端口”怎样进入对象,以及“响应后槽位可回收”怎样证明本次处理已经结束。若两段之间的连续关系无法解释“外部证据与 PLC 在线证据缺一不可。”,就不能把局部代码截图当成实现证据。

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

iecst
    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,
    bError               => GVL_HttpRealTest.bServerError,
    diErrorID            => GVL_HttpRealTest.diServerErrorID,
    sDiagMsg             => GVL_HttpRealTest.sServerDiagMsg,
    eLastNbsError        => GVL_HttpRealTest.eServerLastNbsError,

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

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

iecst
    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,

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

验证路径

场景操作通过口径
端口监听启动 ServerListening、Running 和句柄一致
合法请求curl GET /api/ping返回 200、长度 4、Body 为 pong
错误路径请求 /api/missing返回 404 而不是静默断开
连续执行重复请求 20 次槽位可回收,计数持续递增

场景 1:端口监听

启用 Server 后先不发送请求,只观察 Listening、Running 和监听句柄。三者必须同时成立,通信工具连接目标端口也不能被拒绝。若布尔状态显示运行但句柄无效,应先查 NBS 初始化和端口占用,不能继续把问题归到 HTTP 路由。

场景 2:合法请求

用 curl 发送 GET /api/ping HTTP/1.1,同时记录原始响应和 PLC 在线变量。通过条件不是“浏览器显示了内容”,而是状态行等于 200 OK、Content-Length 等于 4、Body 逐字等于 pong,并且 Server 的请求计数和最近目标同步更新。

场景 3:错误路径

把路径改为 /api/missing。连接仍应正常完成一次 HTTP 事务,但业务结果必须是 404,而不是空响应、TCP 复位或继续返回 pong。这样才能证明路由失败属于应用层结果,不会被错误包装成底层通信故障。

场景 4:连续执行

连续发送 20 次 /api/ping,每次都校验状态码、长度和 Body。结束后活动连接数应回到预期值,请求计数累计增加,连接槽可以再次接入。若前几次正常、随后只能重启 PLC 恢复,优先检查 Close、槽位释放和完成脉冲复位。

常见误判

  • 端口能连接就宣布最小 Server 已经跑通。
  • 只看 curl 的 Body,不核对状态行、Content-Length 和连接回收。
  • 最小验证同时引入 JSON、POST、keep-alive 和真实设备动作,失败后无法分层。

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

这一篇你最该记住

  • 先跑通一笔最小事务,再增加复杂度。
  • 200、Header、Body 和关闭策略必须一起验证。
  • 外部证据与 PLC 在线证据缺一不可。

系列导航

  • 系列:CodeSys HTTP 系列教程,第 05/28 篇。
  • 阶段:服务器篇,职责线位置 1/7。
  • 上一篇:第04篇
  • 下一篇:第06篇
  • 发布顺序:基础认知 -> Server -> Client -> 完整源码加更 -> 综合收束。
评论和回复区

评论区预留

这里先保留评论和回复结构,不接入第三方服务。后续统一决定登录、匿名、审核、反垃圾和静态站兼容策略。

↑ ↓