方案定义
本方案采用两级时基:
系统RTC:用于计算统一周期起点periodStart系统时钟Tick:用于保证发送窗口精度
本方案支持两种通信模式:
主从通信:由 Master 周期发布系统 RTC对等通信:由1号节点周期发布系统 RTC
系统 RTC 发布节点只负责授时,不直接参与其他节点的心跳调度计算。
全局参数
Thb:节点心跳周期N:系统容量NodeId:节点号rtcNow:当前节点本地 RTC 时间localTickNow:当前节点本地系统 Tick
当前参数示例:
Thb = 30sN = 200系统 RTC 发布周期 = 10分钟本地 Tick 精度 = 10ms
系统RTC来源
主从通信模式
系统 RTC 由 Master 周期发布。
Slave 接收后同步本地 RTC。
对等通信模式
系统 RTC 由 1号节点 周期发布, 其他节点接收后同步本地 RTC。
对等通信模式下,1号节点 发送,所有节点仍按统一规则计算心跳周期与发送窗口。
周期起点计算
当前心跳周期起点由节点本地 RTC 直接计算:
c
periodStart = (rtcNow / Thb) * Thb;
其中:
rtcNow为以秒为单位的系统时间戳Thb为以秒为单位的心跳周期
示例:
c
rtcNow = 1719833725;
Thb = 30;
periodStart = (1719833725 / 30) * 30 = 1719833710;
所有节点只要 RTC 已同步到同一系统时间基准,计算得到的 periodStart 就一致。
发送窗口计算
每个节点固定映射到一个发送窗口:
c
windowSize = Thb / N;
sendOffset = (NodeId % N) * windowSize;
参数代入:
c
windowSize = 30000ms / 200 = 150ms;
即在每个 30 秒周期内,200 个节点均匀分散到 200 个发送窗口中。
本地Tick映射
在轮询中根据当前 RTC 计算当前周期起点:
c
periodStartRtc = (rtcNowSec / T_hbSec) * T_hbSec;
若检测到进入新周期,则记录当前本地 Tick:
c
periodStartTick = localTickNow;
该值表示当前周期起点在本地 Tick 时间轴上的映射时刻。
心跳发送规则
本节点发送偏移为:
c
sendOffset = (NodeId % N) * windowSize;
sendTick = periodStartTick + sendOffset;
发送条件:
c
if (!sentFlag && localTickNow >= sendTick) {
send_heartbeat();
sentFlag = 1;
}
每个周期只发送一次心跳。
进入新周期后清除 sentFlag。
RTC同步规则
系统 RTC 发布节点周期发送 RTC 同步报文。
节点收到后执行本地 RTC 同步。
RTC 同步报文只用于:
- 更新本地 RTC
RTC 同步报文不用于:
- 不直接计算
periodStart - 不直接修改发送窗口
- 不直接触发心跳发送
运行流程
1. 节点初始化
初始化上下文参数:
- 心跳周期
Thb - 系统容量
N - 节点号
NodeId - 本周期发送标志
sentFlag
2. 系统RTC同步
系统 RTC 发布节点周期发送 RTC 同步报文, 其他节点接收后同步本地 RTC。
3. 周期轮询
节点周期调用轮询函数,输入:
- 当前 RTC
- 当前本地 Tick
4. 计算周期起点
根据当前 RTC 计算:
c
periodStartRtc = (rtcNowSec / T_hbSec) * T_hbSec;
5. 新周期处理
若 periodStartRtc 发生变化:
- 更新记录的周期起点
- 保存新的
periodStartTick - 清除
sentFlag
6. 发送判定
根据节点号计算发送偏移与发送时刻,满足条件后发送一次心跳。
参考实现
c
#include <stdint.h>
typedef struct {
uint32_t lastPeriodStartRtc; // 当前周期起点,单位:秒
uint32_t periodStartTick; // 当前周期起点对应的本地Tick,单位:ms
uint32_t windowSizeMs; // 窗口宽度,单位:ms
uint32_t periodSec; // 心跳周期,单位:秒
uint16_t nodeId;
uint16_t nodeCount;
uint8_t sentFlag;
} HeartbeatCtx;
void heartbeat_init(HeartbeatCtx *ctx,
uint16_t nodeId,
uint16_t nodeCount,
uint32_t periodSec)
{
ctx->lastPeriodStartRtc = 0;
ctx->periodStartTick = 0;
ctx->windowSizeMs = (periodSec * 1000) / nodeCount;
ctx->periodSec = periodSec;
ctx->nodeId = nodeId;
ctx->nodeCount = nodeCount;
ctx->sentFlag = 0;
}
void heartbeat_poll(HeartbeatCtx *ctx, uint32_t rtcNowSec, uint32_t localTickNow)
{
uint32_t periodStartRtc = (rtcNowSec / ctx->periodSec) * ctx->periodSec;
if (periodStartRtc != ctx->lastPeriodStartRtc) {
ctx->lastPeriodStartRtc = periodStartRtc;
ctx->periodStartTick = localTickNow;
ctx->sentFlag = 0;
}
uint32_t sendOffset =(uint32_t)(ctx->nodeId % ctx->nodeCount) * ctx->windowSizeMs;
uint32_t sendTick = ctx->periodStartTick + sendOffset;
if (!ctx->sentFlag && localTickNow >= sendTick) {
send_heartbeat();
ctx->sentFlag = 1;
}
}
参数示例
按当前项目参数:
nodeCount = 200periodSec = 30windowSizeMs = 150RTC 发布周期 = 10分钟local Tick = 10ms
小结
本方案统一以系统 RTC 计算 periodStart,以节点号分配固定发送窗口,以本地 Tick 实现窗口级精确发送。系统 RTC 可由 Master 发布,也可在对等通信模式下由 1 号机发布。RTC 同步仅用于统一各节点本地时间,不直接参与心跳调度逻辑。