乐于分享
好东西不私藏

源码加更05_订阅表、Retain、PUBLISH 路由和业务事件

源码加更05_订阅表、Retain、PUBLISH 路由和业务事件

源码加更05_订阅表、Retain、PUBLISH 路由和业务事件

这一组源码加更只有一个目标:把 MqttBroker 的真实 ST 源码按工程阅读顺序讲完整。不是再补几段“看起来像源码”的片段,而是让读者能沿着源码对象理解这个 Broker 怎么组织、怎么运行、怎么排障。

适合谁收藏

  • 已经读过 MqttBroker 主线教程,想继续看真实源码实现的工程师。
  • 想学习 CodeSys ST 工程如何拆分 Broker、连接池、编解码、路由和 QoS 调度的人。
  • 想把 MQTT Broker 移植到 PLC、边缘控制器或教学工程里的开发者。

先给结论

这一篇看订阅表和路由器如何协同:订阅进入表,Retain 更新缓存,PUBLISH 按 Topic Filter 匹配 fanout,最后把消息送进连接发送队列。

SUBSCRIBE 不是保存一个字符串,PUBLISH 也不是收到就群发。Broker 的核心价值在于把订阅关系、通配符匹配、Retain 回放和业务事件组织成可控路由。

这篇覆盖 13 个源码文件,合计约 819 行 ST 代码。为了保持公开教程可读性,正文先讲源码阅读路径,再给完整源码。读代码时建议不要从第一个代码块一路机械读到底,而是按本篇的“读代码顺序”来抓主线。

从工程问题到代码职责

层次
本篇重点
你读源码时要抓住的判断
工程入口
程序如何启动、对象如何被实例化
先确认谁是入口,谁只是被调度的对象
数据边界
容量、状态、错误、缓冲区和表结构
先知道边界,后面排障才不会乱猜
协作关系
各 FB、函数和结构体如何互相传递数据
不按文件夹读,按数据流和状态流读
验证路径
在线观察应该看哪些变量
代码最终要能落到现场排障,而不是只停在源码阅读

本篇源码覆盖表

序号
源码对象
行数
1
FB_MqttBroker.M_HandlePublish.st
62
2
FB_MqttBroker.M_HandleSubscribe.st
94
3
FB_MqttBroker.M_HandleUnsubscribe.st
41
4
FB_MqttBroker.M_RoutePublishNow.st
75
5
FB_MqttBrokerRouter.M_AddSubscription.st
66
6
FB_MqttBrokerRouter.M_ClearSlot.st
41
7
FB_MqttBrokerRouter.M_FindNextRetain.st
77
8
FB_MqttBrokerRouter.M_FindNextRoute.st
63
9
FB_MqttBrokerRouter.M_RemoveSubscription.st
47
10
FB_MqttBrokerRouter.M_UpdateRetain.st
86
11
FB_MqttBrokerRouter.st
21
12
F_MqttMinQoS.st
21
13
F_MqttTopicMatch.st
125

推荐阅读顺序

  • 先看 Router 主体和订阅增删。
  • 再看 Retain 查询与更新。
  • 最后看 Broker 顶层如何处理 Publish/Subscribe/Unsubscribe 业务事件。

验证和排障边界

  • 测试精确主题、+# 三类订阅。
  • 测试 Retain 首次订阅回放,以及取消订阅后不再接收。

本篇完整开源代码

下面代码来自对应 .st 源文件的连续完整内容。为方便公开阅读,只保留源码对象名,不放本机工程路径。

完整代码 01: FB_MqttBroker.M_HandlePublish.st

/// =======================================================================/// 名称      : M_HandlePublish/// 功能      : 处理一条入站或 Will PUBLISH/// 说明      : 统一完成入站事务、PUBACK、Retain 更新和订阅路由投递。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_HandlePublish : BOOLVAR_INPUT    uiSourceSlot : UINT; // 发布来源槽位编号,Will 或系统内部发布也使用原异常槽位[1..cnMaxClientSlots]    stPublish    : ST_MqttBrokerPublishFrame; // 需要处理的发布帧END_VAR// === IMPLEMENTATION ===IF NOT stPublish.xValid THEN    M_HandlePublish := FALSE;    RETURN;END_IFIF (uiSourceSlot >= 1) AND (uiSourceSlot <= GVL_MqttBroker.cnMaxClientSlots) THEN    IF NOT M_CheckPublishAcl(uiSlot := uiSourceSlot, sTopic := stPublish.sTopic) THEN        stMetrics.udiAclRejected := stMetrics.udiAclRejected + 1;        M_LogDiag(uiSlot := uiSourceSlot, eError := E_MqttBrokerError.uiInvalidTopic, sMessage := 'Publish topic rejected by ACL');        M_HandlePublish := FALSE;        RETURN;    END_IFEND_IFstMetrics.udiPublishReceived := stMetrics.udiPublishReceived + 1;IF stPublish.eQoS = E_MqttQoS.byQoS1 THEN    IF fbRxScheduler.M_RecordPublish(stPublish := stPublish, udiNowMs := udiNowMs) THEN        IF (uiSourceSlot >= 1) AND (uiSourceSlot <= GVL_MqttBroker.cnMaxClientSlots) THEN            aConnections[uiSourceSlot].M_EnqueueProtocolAck(                ePacketType := E_MqttPacketType.byPubAck,                uiPacketId := stPublish.uiPacketId,                byReturnCode := 0);            fbRxScheduler.M_CompletePublish(uiSlot := uiSourceSlot, uiPacketId := stPublish.uiPacketId);            stMetrics.udiPubAckSent := stMetrics.udiPubAckSent + 1;        END_IF    END_IFEND_IFIF stPublish.eQoS = E_MqttQoS.byQoS2 THEN    IF fbRxScheduler.M_RecordPublish(stPublish := stPublish, udiNowMs := udiNowMs) THEN        IF (uiSourceSlot >= 1) AND (uiSourceSlot <= GVL_MqttBroker.cnMaxClientSlots) THEN            aConnections[uiSourceSlot].M_EnqueueProtocolAck(                ePacketType := E_MqttPacketType.byPubRec,                uiPacketId := stPublish.uiPacketId,                byReturnCode := 0);            stMetrics.udiPubRecSent := stMetrics.udiPubRecSent + 1;        END_IF    END_IF    M_HandlePublish := TRUE;    RETURN;END_IFM_RoutePublishNow(stPublish := stPublish);M_HandlePublish := TRUE;

完整代码 02: FB_MqttBroker.M_HandleSubscribe.st

/// =======================================================================/// 名称      : M_HandleSubscribe/// 功能      : 处理连接槽位输出的多 Topic SUBSCRIBE/// 说明      : 每个 Topic Filter 独立写订阅表、生成 SUBACK 返回码,并触发 Retain 补发。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_HandleSubscribe : BOOLVAR_INPUT    uiSlot       : UINT; // 发起 SUBSCRIBE 的客户端槽位编号[1..cnMaxClientSlots]END_VARVAR    uiItemIndex  : UINT; // SUBSCRIBE 多 Topic 条目扫描索引[1..cnMaxTopicItemsPerPacket]    uiRetainReplayCount : UINT; // 本次 SUBSCRIBE 已补发的 Retain 消息数量[条]    aReturnCodes : ARRAY[1..GVL_MqttBroker.cnMaxTopicItemsPerPacket] OF BYTE; // SUBACK 多 Topic 返回码数组    uiReturnCount : UINT; // SUBACK 返回码数量[条]END_VAR// === IMPLEMENTATION ===IF (uiSlot < 1) OR (uiSlot > GVL_MqttBroker.cnMaxClientSlots) THEN    M_HandleSubscribe := FALSE;    RETURN;END_IFuiReturnCount := 0;uiRetainReplayCount := 0;FOR uiItemIndex := 1 TO aConnections[uiSlot].uiSubItemCount DO    IF uiItemIndex > GVL_MqttBroker.cnMaxTopicItemsPerPacket THEN        EXIT;    END_IF    uiReturnCount := uiReturnCount + 1;    IF aConnections[uiSlot].aSubItems[uiItemIndex].xUsed        AND M_CheckSubscribeAcl(uiSlot := uiSlot, sTopicFilter := aConnections[uiSlot].aSubItems[uiItemIndex].sTopicFilter)        AND fbRouter.M_AddSubscription(            uiSlot := uiSlot,            sClientId := aConnectionStates[uiSlot].sClientId,            sTopicFilter := aConnections[uiSlot].aSubItems[uiItemIndex].sTopicFilter,            eMaxQoS := aConnections[uiSlot].aSubItems[uiItemIndex].eQoS) THEN        aReturnCodes[uiReturnCount] := TO_BYTE(aConnections[uiSlot].aSubItems[uiItemIndex].eQoS);        IF fbRouter.M_FindNextRetain(            stDelivery := stDelivery,            uiTargetSlot := uiSlot,            sTopicFilter := aConnections[uiSlot].aSubItems[uiItemIndex].sTopicFilter,            eMaxQoS := aConnections[uiSlot].aSubItems[uiItemIndex].eQoS,            xRestart := TRUE,            xFound => xRetainFound) THEN            WHILE xRetainFound AND (uiRetainReplayCount < GVL_MqttBroker.cnMaxRetainReplayPerScan) DO                IF aConnections[uiSlot].M_EnqueueDelivery(stDelivery := stDelivery) THEN                    stMetrics.udiPublishDelivered := stMetrics.udiPublishDelivered + 1;                    sLastRetainTopic := stDelivery.sTopic;                    uiLastRetainPayloadLen := stDelivery.uiPayloadLen;                    uiRetainReplayCount := uiRetainReplayCount + 1;                    IF stDelivery.eQoS <> E_MqttQoS.byQoS0 THEN                        // Retain 补发本质上也是 Broker 到订阅者方向的出站 PUBLISH。                        // 当补发 QoS>0 时,必须登记出站事务,否则订阅者后续 PUBACK/PUBREC 无法正确收口。                        fbTxScheduler.M_RegisterPublish(stPublish := stDelivery, udiNowMs := udiNowMs);                    END_IF                END_IF                fbRouter.M_FindNextRetain(                    stDelivery := stDelivery,                    uiTargetSlot := uiSlot,                    sTopicFilter := aConnections[uiSlot].aSubItems[uiItemIndex].sTopicFilter,                    eMaxQoS := aConnections[uiSlot].aSubItems[uiItemIndex].eQoS,                    xRestart := FALSE,                    xFound => xRetainFound);            END_WHILE            IF xRetainFound THEN                // 默认预算等于 Retain 表容量,因此正常配置下不会漏发。                // 如果工程师为了扫描周期把 cnMaxRetainReplayPerScan 调小,本诊断能明确提示本次订阅仍有 Retain 未在同一扫描周期补发。                M_LogDiag(uiSlot := uiSlot, eError := E_MqttBrokerError.uiQueueFull, sMessage := 'Retain replay budget exhausted');            END_IF        END_IF    ELSE        aReturnCodes[uiReturnCount] := 16#80;        stMetrics.udiAclRejected := stMetrics.udiAclRejected + 1;    END_IFEND_FORaConnections[uiSlot].M_EnqueueProtocolAckList(    aReturnCodes := aReturnCodes,    ePacketType := E_MqttPacketType.bySubAck,    uiPacketId := aConnections[uiSlot].uiSubPacketId,    uiReturnCount := uiReturnCount);M_HandleSubscribe := TRUE;

完整代码 03: FB_MqttBroker.M_HandleUnsubscribe.st

/// =======================================================================/// 名称      : M_HandleUnsubscribe/// 功能      : 处理连接槽位输出的多 Topic UNSUBSCRIBE/// 说明      : 每个 Topic Filter 独立从订阅表删除,最后返回一个 UNSUBACK。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_HandleUnsubscribe : BOOLVAR_INPUT    uiSlot      : UINT; // 发起 UNSUBSCRIBE 的客户端槽位编号[1..cnMaxClientSlots]END_VARVAR    uiItemIndex : UINT; // UNSUBSCRIBE 多 Topic 条目扫描索引[1..cnMaxTopicItemsPerPacket]END_VAR// === IMPLEMENTATION ===IF (uiSlot < 1) OR (uiSlot > GVL_MqttBroker.cnMaxClientSlots) THEN    M_HandleUnsubscribe := FALSE;    RETURN;END_IFFOR uiItemIndex := 1 TO aConnections[uiSlot].uiUnsubItemCount DO    IF uiItemIndex > GVL_MqttBroker.cnMaxTopicItemsPerPacket THEN        EXIT;    END_IF    IF aConnections[uiSlot].aUnsubItems[uiItemIndex].xUsed THEN        fbRouter.M_RemoveSubscription(            uiSlot := uiSlot,            sTopicFilter := aConnections[uiSlot].aUnsubItems[uiItemIndex].sTopicFilter);    END_IFEND_FORaConnections[uiSlot].M_EnqueueProtocolAck(    ePacketType := E_MqttPacketType.byUnsubAck,    uiPacketId := aConnections[uiSlot].uiUnsubPacketId,    byReturnCode := 0);M_HandleUnsubscribe := TRUE;

完整代码 04: FB_MqttBroker.M_RoutePublishNow.st

/// =======================================================================/// 名称      : M_RoutePublishNow/// 功能      : 对已确认可路由的发布帧执行 Retain 与订阅路由/// 说明      : QoS0/QoS1 直接调用;QoS2 仅在首次 PUBREL 时调用,保证 exactly once。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_RoutePublishNow : BOOLVAR_INPUT    stPublish    : ST_MqttBrokerPublishFrame; // 已确认允许执行路由的发布帧END_VARVAR    uiRouteCount : UINT; // 本次发布已经生成的路由投递数量    stRouteFrame : ST_MqttBrokerPublishFrame; // 路由器返回的单条投递任务END_VAR// === IMPLEMENTATION ===IF NOT stPublish.xValid THEN    M_RoutePublishNow := FALSE;    RETURN;END_IFIF stPublish.xRetain THEN    IF fbRouter.M_UpdateRetain(stPublish := stPublish, udiNowMs := udiNowMs) THEN        sLastRetainTopic := stPublish.sTopic;        uiLastRetainPayloadLen := stPublish.uiPayloadLen;        uiRetainCount := fbRouter.uiRetainCount;        IF stPublish.uiPayloadLen = 0 THEN            stMetrics.udiRetainCleared := stMetrics.udiRetainCleared + 1;        ELSE            stMetrics.udiRetainUpdated := stMetrics.udiRetainUpdated + 1;        END_IF    END_IFEND_IFuiRouteCount := 0;IF fbRouter.M_FindNextRoute(    stDelivery := stRouteFrame,    stPublish := stPublish,    xRestart := TRUE,    xFound => xRouteFound) THEN    WHILE xRouteFound AND (uiRouteCount < GVL_MqttBroker.cnMaxRouteFanoutPerScan) DO        IF (stRouteFrame.uiTargetSlot >= 1) AND (stRouteFrame.uiTargetSlot <= GVL_MqttBroker.cnMaxClientSlots) THEN            IF aConnections[stRouteFrame.uiTargetSlot].M_EnqueueDelivery(stDelivery := stRouteFrame) THEN                stMetrics.udiPublishDelivered := stMetrics.udiPublishDelivered + 1;                uiRouteCount := uiRouteCount + 1;                IF stRouteFrame.eQoS <> E_MqttQoS.byQoS0 THEN                    // M_EnqueueDelivery 会为首次 QoS>0 投递分配“目标连接内”的 PacketId。                    // 事务表必须登记这个已分配后的 PacketId,后续订阅者 PUBACK 才能正确清理事务。                    fbTxScheduler.M_RegisterPublish(stPublish := stRouteFrame, udiNowMs := udiNowMs);                END_IF            ELSE                stMetrics.udiPublishDropped := stMetrics.udiPublishDropped + 1;            END_IF        END_IF        fbRouter.M_FindNextRoute(            stDelivery := stRouteFrame,            stPublish := stPublish,            xRestart := FALSE,            xFound => xRouteFound);    END_WHILE    IF xRouteFound THEN        // 路由扇出预算耗尽时保留诊断,不把它伪装成“没有订阅者”。        // 当前轻量实现不缓存剩余路由任务,默认 cnMaxRouteFanoutPerScan 与客户端规模一致,正常 5~8 客户端不会丢投。        M_LogDiag(uiSlot := stPublish.uiSourceSlot, eError := E_MqttBrokerError.uiQueueFull, sMessage := 'Route fanout budget exhausted');    END_IFEND_IFM_RoutePublishNow := TRUE;

完整代码 05: FB_MqttBrokerRouter.M_AddSubscription.st

/// =======================================================================/// 名称      : M_AddSubscription/// 功能      : 新增或更新订阅表项/// 说明      : 同一槽位同一 Topic Filter 重复订阅时更新 QoS,而不是新增重复项。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_AddSubscription : BOOLVAR_INPUT    uiSlot       : UINT; // 订阅所属客户端槽位编号[1..cnMaxClientSlots]    sClientId    : STRING; // 订阅所属 ClientID 快照    sTopicFilter : STRING; // 客户端请求的 Topic Filter    eMaxQoS      : E_MqttQoS; // 客户端请求的最大 QoSEND_VARVAR    uiIndex      : UINT; // 订阅表扫描索引    uiFreeIndex  : UINT; // 首个空闲订阅表槽位索引END_VAR// === IMPLEMENTATION ===IF (uiSlot < 1) OR (uiSlot > GVL_MqttBroker.cnMaxClientSlots) THEN    eLastError := E_MqttBrokerError.uiInvalidState;    M_AddSubscription := FALSE;    RETURN;END_IFIF NOT F_MqttIsValidTopicFilter(sFilter := sTopicFilter) THEN    eLastError := E_MqttBrokerError.uiInvalidTopic;    M_AddSubscription := FALSE;    RETURN;END_IFuiFreeIndex := 0;FOR uiIndex := 1 TO GVL_MqttBroker.cnMaxSubscriptions DO    IF aSubscriptions[uiIndex].xUsed THEN        IF (aSubscriptions[uiIndex].uiSlot = uiSlot) AND (aSubscriptions[uiIndex].sTopicFilter = sTopicFilter) THEN            aSubscriptions[uiIndex].eMaxQoS := eMaxQoS;            aSubscriptions[uiIndex].xActive := TRUE;            eLastError := E_MqttBrokerError.uiNoError;            M_AddSubscription := TRUE;            RETURN;        END_IF    ELSIF uiFreeIndex = 0 THEN        uiFreeIndex := uiIndex;    END_IFEND_FORIF uiFreeIndex = 0 THEN    eLastError := E_MqttBrokerError.uiSubscriptionFull;    M_AddSubscription := FALSE;    RETURN;END_IFaSubscriptions[uiFreeIndex].xUsed := TRUE;aSubscriptions[uiFreeIndex].xActive := TRUE;aSubscriptions[uiFreeIndex].uiSlot := uiSlot;aSubscriptions[uiFreeIndex].sClientId := sClientId;aSubscriptions[uiFreeIndex].sTopicFilter := sTopicFilter;aSubscriptions[uiFreeIndex].uiFilterLen := TO_UINT(LEN(sTopicFilter));aSubscriptions[uiFreeIndex].eMaxQoS := eMaxQoS;uiSubscriptionCount := uiSubscriptionCount + 1;eLastError := E_MqttBrokerError.uiNoError;M_AddSubscription := TRUE;

完整代码 06: FB_MqttBrokerRouter.M_ClearSlot.st

/// =======================================================================/// 名称      : M_ClearSlot/// 功能      : 清理指定连接槽位的订阅资源/// 说明      : Clean Session 或连接释放时调用,避免断线客户端继续收到路由任务。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_ClearSlot : BOOLVAR_INPUT    uiSlot : UINT; // 需要清理的客户端槽位编号[1..cnMaxClientSlots]END_VARVAR    uiIndex : UINT; // 订阅表扫描索引END_VAR// === IMPLEMENTATION ===IF (uiSlot < 1) OR (uiSlot > GVL_MqttBroker.cnMaxClientSlots) THEN    eLastError := E_MqttBrokerError.uiInvalidState;    M_ClearSlot := FALSE;    RETURN;END_IFFOR uiIndex := 1 TO GVL_MqttBroker.cnMaxSubscriptions DO    IF aSubscriptions[uiIndex].xUsed AND (aSubscriptions[uiIndex].uiSlot = uiSlot) THEN        aSubscriptions[uiIndex].xUsed := FALSE;        aSubscriptions[uiIndex].xActive := FALSE;        aSubscriptions[uiIndex].uiSlot := 0;        aSubscriptions[uiIndex].sClientId := '';        aSubscriptions[uiIndex].sTopicFilter := '';        aSubscriptions[uiIndex].uiFilterLen := 0;        aSubscriptions[uiIndex].eMaxQoS := E_MqttQoS.byQoS0;        IF uiSubscriptionCount > 0 THEN            uiSubscriptionCount := uiSubscriptionCount - 1;        END_IF    END_IFEND_FOReLastError := E_MqttBrokerError.uiNoError;M_ClearSlot := TRUE;

完整代码 07: FB_MqttBrokerRouter.M_FindNextRetain.st

/// =======================================================================/// 名称      : M_FindNextRetain/// 功能      : 查找新订阅命中的下一条 Retain 消息/// 说明      : SUBSCRIBE 成功后循环调用,生成需要补发给订阅者的 Retain 投递任务。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_FindNextRetain : BOOLVAR_INPUT    uiTargetSlot   : UINT; // 新订阅客户端槽位编号[1..cnMaxClientSlots]    sTopicFilter   : STRING; // 新订阅的 Topic Filter    eMaxQoS        : E_MqttQoS; // 新订阅请求的最大 QoS    xRestart       : BOOL; // TRUE 表示从 Retain 表起点重新扫描END_VARVAR_IN_OUT    stDelivery     : ST_MqttBrokerPublishFrame; // 返回给调度层的 Retain 投递任务END_VARVAR_OUTPUT    xFound         : BOOL; // TRUE 表示本次找到一条命中 Retain 消息END_VARVAR    uiIndex        : UINT; // Retain 表扫描索引END_VAR// === IMPLEMENTATION ===xFound := FALSE;IF xRestart THEN    uiScanIndex := 1;END_IFIF (uiTargetSlot < 1) OR (uiTargetSlot > GVL_MqttBroker.cnMaxClientSlots) THEN    eLastError := E_MqttBrokerError.uiInvalidState;    M_FindNextRetain := FALSE;    RETURN;END_IFIF NOT F_MqttIsValidTopicFilter(sFilter := sTopicFilter) THEN    eLastError := E_MqttBrokerError.uiInvalidTopic;    M_FindNextRetain := FALSE;    RETURN;END_IFIF uiScanIndex < 1 THEN    uiScanIndex := 1;END_IFFOR uiIndex := uiScanIndex TO GVL_MqttBroker.cnMaxRetainedMessages DO    uiScanIndex := uiIndex + 1;    // 与普通路由一样,Retain 匹配也必须先确认表项有效,再调用 Topic 匹配函数。    // 这能避免运行时非短路求值导致空 Retain 主题参与匹配。    IF aRetained[uiIndex].xUsed THEN        IF F_MqttTopicMatch(sFilter := sTopicFilter, sTopic := aRetained[uiIndex].sTopic) THEN            stDelivery.xValid := TRUE;            stDelivery.uiSourceSlot := 0;            stDelivery.uiTargetSlot := uiTargetSlot;            stDelivery.uiPacketId := 0;            stDelivery.eQoS := F_MqttMinQoS(ePublishQoS := aRetained[uiIndex].eQoS, eMaxQoS := eMaxQoS);            stDelivery.xDup := FALSE;            stDelivery.xRetain := TRUE;            stDelivery.uiTopicLen := aRetained[uiIndex].uiTopicLen;            stDelivery.uiPayloadLen := aRetained[uiIndex].uiPayloadLen;            stDelivery.sTopic := aRetained[uiIndex].sTopic;            stDelivery.sPayload := aRetained[uiIndex].sPayload;            xFound := TRUE;            eLastError := E_MqttBrokerError.uiNoError;            M_FindNextRetain := TRUE;            RETURN;        END_IF    END_IFEND_FOReLastError := E_MqttBrokerError.uiNoError;M_FindNextRetain := TRUE;

完整代码 08: FB_MqttBrokerRouter.M_FindNextRoute.st

/// =======================================================================/// 名称      : M_FindNextRoute/// 功能      : 查找发布消息的下一条订阅匹配结果/// 说明      : 外部循环调用本方法生成投递任务,每次最多返回一条,避免一次扫描阻塞扫描周期。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_FindNextRoute : BOOLVAR_INPUT    stPublish      : ST_MqttBrokerPublishFrame; // 需要路由的发布帧    xRestart       : BOOL; // TRUE 表示从订阅表起点重新扫描END_VARVAR_IN_OUT    stDelivery     : ST_MqttBrokerPublishFrame; // 返回给调度层的投递任务END_VARVAR_OUTPUT    xFound         : BOOL; // TRUE 表示本次找到一条匹配订阅END_VARVAR    uiIndex        : UINT; // 订阅表扫描索引END_VAR// === IMPLEMENTATION ===xFound := FALSE;stDelivery := stPublish;IF xRestart THEN    uiScanIndex := 1;END_IFIF NOT stPublish.xValid THEN    M_FindNextRoute := FALSE;    RETURN;END_IFIF uiScanIndex < 1 THEN    uiScanIndex := 1;END_IFFOR uiIndex := uiScanIndex TO GVL_MqttBroker.cnMaxSubscriptions DO    uiScanIndex := uiIndex + 1;    // IEC ST 不要求 AND 必须短路求值,Topic 匹配这类带字符串下标的函数必须放到最内层。    // 这样未使用或未激活的订阅不会进入 F_MqttTopicMatch,避免空字符串/旧数据干扰路由。    // MQTT 3.1.1 没有 noLocal 订阅选项,发布者如果自己也订阅了同一主题,也应该收到 Broker 回投。    IF aSubscriptions[uiIndex].xUsed THEN        IF aSubscriptions[uiIndex].xActive THEN            IF F_MqttTopicMatch(sFilter := aSubscriptions[uiIndex].sTopicFilter, sTopic := stPublish.sTopic) THEN                stDelivery.uiTargetSlot := aSubscriptions[uiIndex].uiSlot;                stDelivery.uiPacketId := 0;                stDelivery.eQoS := F_MqttMinQoS(ePublishQoS := stPublish.eQoS, eMaxQoS := aSubscriptions[uiIndex].eMaxQoS);                stDelivery.xDup := FALSE;                xFound := TRUE;                M_FindNextRoute := TRUE;                RETURN;            END_IF        END_IF    END_IFEND_FORM_FindNextRoute := TRUE;

完整代码 09: FB_MqttBrokerRouter.M_RemoveSubscription.st

/// =======================================================================/// 名称      : M_RemoveSubscription/// 功能      : 删除指定槽位的订阅表项/// 说明      : UNSUBSCRIBE 成功解析后调用,按槽位和 Topic Filter 精确删除。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_RemoveSubscription : BOOLVAR_INPUT    uiSlot       : UINT; // 取消订阅所属客户端槽位编号[1..cnMaxClientSlots]    sTopicFilter : STRING; // 需要删除的 Topic FilterEND_VARVAR    uiIndex      : UINT; // 订阅表扫描索引END_VAR// === IMPLEMENTATION ===IF (uiSlot < 1) OR (uiSlot > GVL_MqttBroker.cnMaxClientSlots) THEN    eLastError := E_MqttBrokerError.uiInvalidState;    M_RemoveSubscription := FALSE;    RETURN;END_IFFOR uiIndex := 1 TO GVL_MqttBroker.cnMaxSubscriptions DO    IF aSubscriptions[uiIndex].xUsed        AND (aSubscriptions[uiIndex].uiSlot = uiSlot)        AND (aSubscriptions[uiIndex].sTopicFilter = sTopicFilter) THEN        aSubscriptions[uiIndex].xUsed := FALSE;        aSubscriptions[uiIndex].xActive := FALSE;        aSubscriptions[uiIndex].uiSlot := 0;        aSubscriptions[uiIndex].sClientId := '';        aSubscriptions[uiIndex].sTopicFilter := '';        aSubscriptions[uiIndex].uiFilterLen := 0;        aSubscriptions[uiIndex].eMaxQoS := E_MqttQoS.byQoS0;        IF uiSubscriptionCount > 0 THEN            uiSubscriptionCount := uiSubscriptionCount - 1;        END_IF        eLastError := E_MqttBrokerError.uiNoError;        M_RemoveSubscription := TRUE;        RETURN;    END_IFEND_FOReLastError := E_MqttBrokerError.uiNoError;M_RemoveSubscription := TRUE;

完整代码 10: FB_MqttBrokerRouter.M_UpdateRetain.st

/// =======================================================================/// 名称      : M_UpdateRetain/// 功能      : 更新或清除 Retain 保留消息/// 说明      : Retain PUBLISH 载荷为空时清除对应 Topic,否则新增或覆盖保存。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}METHOD M_UpdateRetain : BOOLVAR_INPUT    stPublish      : ST_MqttBrokerPublishFrame; // 收到的 Retain PUBLISH 标准发布帧    udiNowMs       : ULINT; // 当前系统时间戳[ms]END_VARVAR    uiIndex        : UINT; // Retain 表扫描索引    uiFreeIndex    : UINT; // 首个空闲 Retain 表槽位索引END_VAR// === IMPLEMENTATION ===IF NOT stPublish.xRetain THEN    M_UpdateRetain := TRUE;    RETURN;END_IFIF NOT F_MqttIsValidTopicName(sTopic := stPublish.sTopic) THEN    eLastError := E_MqttBrokerError.uiInvalidTopic;    M_UpdateRetain := FALSE;    RETURN;END_IFuiFreeIndex := 0;FOR uiIndex := 1 TO GVL_MqttBroker.cnMaxRetainedMessages DO    IF aRetained[uiIndex].xUsed THEN        IF aRetained[uiIndex].sTopic = stPublish.sTopic THEN            IF stPublish.uiPayloadLen = 0 THEN                aRetained[uiIndex].xUsed := FALSE;                aRetained[uiIndex].sTopic := '';                aRetained[uiIndex].sPayload := '';                aRetained[uiIndex].uiTopicLen := 0;                aRetained[uiIndex].uiPayloadLen := 0;                aRetained[uiIndex].eQoS := E_MqttQoS.byQoS0;                aRetained[uiIndex].udiUpdatedAtMs := udiNowMs;                IF uiRetainCount > 0 THEN                    uiRetainCount := uiRetainCount - 1;                END_IF            ELSE                aRetained[uiIndex].eQoS := stPublish.eQoS;                aRetained[uiIndex].uiTopicLen := stPublish.uiTopicLen;                aRetained[uiIndex].uiPayloadLen := stPublish.uiPayloadLen;                aRetained[uiIndex].udiUpdatedAtMs := udiNowMs;                aRetained[uiIndex].sTopic := stPublish.sTopic;                aRetained[uiIndex].sPayload := stPublish.sPayload;            END_IF            eLastError := E_MqttBrokerError.uiNoError;            M_UpdateRetain := TRUE;            RETURN;        END_IF    ELSIF uiFreeIndex = 0 THEN        uiFreeIndex := uiIndex;    END_IFEND_FORIF stPublish.uiPayloadLen = 0 THEN    eLastError := E_MqttBrokerError.uiNoError;    M_UpdateRetain := TRUE;    RETURN;END_IFIF uiFreeIndex = 0 THEN    eLastError := E_MqttBrokerError.uiRetainFull;    M_UpdateRetain := FALSE;    RETURN;END_IFaRetained[uiFreeIndex].xUsed := TRUE;aRetained[uiFreeIndex].eQoS := stPublish.eQoS;aRetained[uiFreeIndex].uiTopicLen := stPublish.uiTopicLen;aRetained[uiFreeIndex].uiPayloadLen := stPublish.uiPayloadLen;aRetained[uiFreeIndex].udiUpdatedAtMs := udiNowMs;aRetained[uiFreeIndex].sTopic := stPublish.sTopic;aRetained[uiFreeIndex].sPayload := stPublish.sPayload;uiRetainCount := uiRetainCount + 1;eLastError := E_MqttBrokerError.uiNoError;M_UpdateRetain := TRUE;

完整代码 11: FB_MqttBrokerRouter.st

/// =======================================================================/// 名称      : FB_MqttBrokerRouter/// 功能      : MQTT Broker 主题路由与 Retain 管理/// 说明      : 维护全局订阅表和 Retain 表,只生成投递任务,不直接执行 TCP 发送。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}FUNCTION_BLOCK FB_MqttBrokerRouterVAR_OUTPUT    uiSubscriptionCount : UINT// 当前全局有效订阅数量    uiRetainCount       : UINT// 当前 Retain 表有效消息数量    eLastError          : E_MqttBrokerError; // 路由器最近一次错误码END_VARVAR    aSubscriptions : ARRAY[1..GVL_MqttBroker.cnMaxSubscriptions] OF ST_MqttBrokerSubscription; // 全局订阅表    aRetained      : ARRAY[1..GVL_MqttBroker.cnMaxRetainedMessages] OF ST_MqttBrokerRetainedMessage; // 全局 Retain 表    uiScanIndex    : UINT// 路由扫描时使用的订阅表索引END_VAR// === IMPLEMENTATION ===

完整代码 12: F_MqttMinQoS.st

/// =======================================================================/// 名称      : F_MqttMinQoS/// 功能      : 选择发布 QoS 与订阅最大 QoS 中较低等级/// 说明      : MQTT Broker 投递时不能超过订阅者请求的最大 QoS。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.0/// ======================================================================={attribute 'hide_all_locals'}FUNCTION F_MqttMinQoS : E_MqttQoSVAR_INPUT    ePublishQoS : E_MqttQoS; // 发布者原始 PUBLISH QoS 等级    eMaxQoS     : E_MqttQoS; // 订阅者在 SUBSCRIBE 中请求的最大 QoS 等级END_VAR// === IMPLEMENTATION ===IF TO_BYTE(ePublishQoS) <= TO_BYTE(eMaxQoS) THEN    F_MqttMinQoS := ePublishQoS;ELSE    F_MqttMinQoS := eMaxQoS;END_IF

完整代码 13: F_MqttTopicMatch.st

/// =======================================================================/// 名称      : F_MqttTopicMatch/// 功能      : MQTT Topic Filter 与 Topic Name 匹配/// 说明      : 支持 + 单层通配符和 # 多层通配符,用于 Broker 路由扫描订阅表。/// 编程人员  : ControlRookie/// 时间      : 2026-05-08/// 版本      : V1.1/// ======================================================================={attribute 'hide_all_locals'}FUNCTION F_MqttTopicMatch : BOOLVAR_INPUT    sFilter : STRING; // 订阅表中的 Topic Filter,可包含 + 或 #    sTopic  : STRING; // PUBLISH 报文中的 Topic Name,不允许包含通配符END_VARVAR    uiFilterLen  : UINT; // Topic Filter 长度[byte]    uiTopicLen   : UINT; // Topic Name 长度[byte]    uiFilterPos  : UINT; // 当前检查的 Topic Filter 字符位置,CodeSys/CODESYS STRING 下标从 0 开始[byte]    uiTopicPos   : UINT; // 当前检查的 Topic Name 字符位置,CodeSys/CODESYS STRING 下标从 0 开始[byte]    byFilterChar : BYTE; // 当前 Topic Filter 字符    byTopicChar  : BYTE; // 当前 Topic Name 字符END_VAR// === IMPLEMENTATION ===IF NOT F_MqttIsValidTopicFilter(sFilter := sFilter) THEN    F_MqttTopicMatch := FALSE;    RETURN;END_IFIF NOT F_MqttIsValidTopicName(sTopic := sTopic) THEN    F_MqttTopicMatch := FALSE;    RETURN;END_IFuiFilterLen := TO_UINT(LEN(sFilter));uiTopicLen := TO_UINT(LEN(sTopic));uiFilterPos := 0;uiTopicPos := 0;// 关键坑位:// CodeSys/CODESYS 的 STRING 字符访问按 0 基下标工作,和很多工程师直觉中的 1 基字符串不同。// CONNECT 协议名、SUBSCRIBE Topic Filter、PUBLISH Topic/Payload 都会经过这类字符串函数。// 如果这里按 1 基访问,现象通常不是立即编译失败,而是订阅路由错位、发布后客户端被协议错误断开。WHILE (uiFilterPos < uiFilterLen) AND (uiTopicPos < uiTopicLen) DO    byFilterChar := sFilter[uiFilterPos];    byTopicChar := sTopic[uiTopicPos];    CASE byFilterChar OF        16#23:            // # 匹配当前层级以及后续所有层级;合法性已由 F_MqttIsValidTopicFilter 保证。            F_MqttTopicMatch := TRUE;            RETURN;        16#2B:            // + 匹配一个完整层级:跳过 Topic 中直到下一个 / 之前的所有字符。            WHILE uiTopicPos < uiTopicLen DO                IF sTopic[uiTopicPos] = 16#2F THEN                    EXIT;                END_IF                uiTopicPos := uiTopicPos + 1;            END_WHILE            uiFilterPos := uiFilterPos + 1;            IF uiFilterPos < uiFilterLen THEN                IF sFilter[uiFilterPos] = 16#2F THEN                    IF uiTopicPos < uiTopicLen THEN                        IF sTopic[uiTopicPos] = 16#2F THEN                            uiFilterPos := uiFilterPos + 1;                            uiTopicPos := uiTopicPos + 1;                        ELSE                            F_MqttTopicMatch := FALSE;                            RETURN;                        END_IF                    ELSE                        F_MqttTopicMatch := FALSE;                        RETURN;                    END_IF                END_IF            END_IF        16#2F:            IF byTopicChar <> 16#2F THEN                F_MqttTopicMatch := FALSE;                RETURN;            END_IF            uiFilterPos := uiFilterPos + 1;            uiTopicPos := uiTopicPos + 1;    ELSE            IF byFilterChar <> byTopicChar THEN                F_MqttTopicMatch := FALSE;                RETURN;            END_IF            uiFilterPos := uiFilterPos + 1;            uiTopicPos := uiTopicPos + 1;    END_CASEEND_WHILEIF (uiFilterPos = uiFilterLen) AND (uiTopicPos = uiTopicLen) THEN    F_MqttTopicMatch := TRUE;ELSE    F_MqttTopicMatch := FALSE;    // IEC ST 的 AND 在部分运行时不保证像高级语言一样短路求值。    // 因此带 STRING 下标访问的边界判断必须拆成嵌套 IF,不能写成一个长布尔表达式。    IF uiFilterPos < uiFilterLen THEN        IF sFilter[uiFilterPos] = 16#23 THEN            F_MqttTopicMatch := TRUE;        ELSIF sFilter[uiFilterPos] = 16#2B THEN            IF (uiFilterPos + 1) = uiFilterLen THEN                // + 可以匹配一个空层级,例如 sport/+ 应匹配 sport/。                F_MqttTopicMatch := TRUE;            END_IF        ELSIF uiTopicPos = uiTopicLen THEN            IF (uiFilterPos + 1) < uiFilterLen THEN                IF sFilter[uiFilterPos] = 16#2F THEN                    IF sFilter[uiFilterPos + 1] = 16#23 THEN                        // MQTT 通配规则允许 sport/# 匹配 sport;这里处理 Topic 已结束但 Filter 还剩 /# 的场景。                        F_MqttTopicMatch := TRUE;                    END_IF                END_IF            END_IF        END_IF    END_IFEND_IF

这一篇你最该记住的几句话

  • Broker 源码不要按“文件夹顺序”读,要按“入口、状态、数据、报文、路由、事务”读。
  • CodeSys ST 工程最容易失控的不是语法,而是对象职责边界混乱。
  • 只要你能把本篇源码对象和在线变量对应起来,后续排查连接、订阅、发布和 QoS 问题就不会乱。

系列导航

  • 第 1 篇:源码加更01_Broker 工程入口、容量边界和数据模型
  • 第 2 篇:源码加更02_FB_MqttBroker 顶层调度、连接池和权限边界
  • 第 3 篇:源码加更03_单连接槽位、TCP 字节流和发送队列
  • 第 4 篇:源码加更04_MQTT 编解码器和字节工具函数
  • 第 5 篇:源码加更05_订阅表、Retain、PUBLISH 路由和业务事件
  • 第 6 篇:源码加更06_QoS 事务调度、重试和生产级闭环
基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 15:24:05 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/831700.html
  2. 运行时间 : 0.108414s [ 吞吐率:9.22req/s ] 内存消耗:4,817.29kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=666a336e94c92920003c4bf6f8bf0518
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000609s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000772s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000309s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000260s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000504s ]
  6. SELECT * FROM `set` [ RunTime:0.000200s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000490s ]
  8. SELECT * FROM `article` WHERE `id` = 831700 LIMIT 1 [ RunTime:0.000799s ]
  9. UPDATE `article` SET `lasttime` = 1783063445 WHERE `id` = 831700 [ RunTime:0.013216s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000280s ]
  11. SELECT * FROM `article` WHERE `id` < 831700 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000516s ]
  12. SELECT * FROM `article` WHERE `id` > 831700 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000389s ]
  13. SELECT * FROM `article` WHERE `id` < 831700 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000674s ]
  14. SELECT * FROM `article` WHERE `id` < 831700 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001130s ]
  15. SELECT * FROM `article` WHERE `id` < 831700 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.011253s ]
0.110104s