BRCB 缓存报告机制全解析
BRCB 缓存报告机制全解析
1. 总体架构
BRCB (Buffered Report Control Block) 报告有三层:
应用层: iec61850_put_data() → writedata_*() → mvlu_rpt_va_change2()
│
▼
缓存层: mvl61850_brcb_rpt_save() → 存入链表缓冲区 (BUFLIST_ENTRY)
│
▼
发送层: mvl61850_rpt_service() → mvl61850_brcb_client_service()
↓ (每个主循环周期被调用)
mvl61850_rcb_chk_state()
↓
BufTim 到期 或 IntgPd 到期
↓
mvlu_rpt_ready() → mvl61850_brcb_rpt_send() → MMS 编码 → TCP 发送
2. mvl61850_brcb_rpt_save() — 缓存写入
// mvl61850_rpt.c:501
ST_RET mvl61850_brcb_rpt_save(MVLU_RPT_CLIENT *rptClient)
{
// Step 1: 如果是 GI 报告,先丢弃缓冲区中旧的 GI 报告
if (rptClient->reasons_data[0] == MVLU_TRGOPS_GI)
buflist_discard_old_gi(&rptClient->rpt_ctrl->brcbCtrl);
// Step 2: 创建新的 BUFLIST_ENTRY,拷贝原始数据到缓冲区
buflist_entry_create(rptClient);
// Step 3: 检查缓冲区是否溢出,如果超限则丢弃最旧的报告
buflist_discard_old(&rptClient->rpt_ctrl->brcbCtrl);
// Step 4: 清理 RCB 状态,准备下次触发
mvl61850_rcb_cleanup(rptClient);
return SD_SUCCESS;
}
buflist_entry_create()(第 299 行)具体操作:
对于数据集中每个变化了的变量(j):
├─ 分配 VAR_DATA.data_ptr = malloc(data_len) ← 深拷贝数据
├─ memcpy(data_ptr, var_assoc->data, data_len) ← 保存当前值快照
├─ 记录 reason_for_incl = reasons_data[j] ← 触发原因 (data/q/chg)
└─ brcbCtrl->cur_bufsize += data_len ← 累加缓冲区用量
填充其他信息:
├─ EntryID: basrcb->EntryID[4..7]++ (自增 4 字节) ← 每个缓存的报告唯一ID
├─ TimeOfEntry: u_mvlu_rpt_time_get() ← 取当前时间戳
└─ asn1_len_array: 预计算每个变量的 ASN.1 编码长度 ← 为分片做准备
链入链表:
list_add_last(&brcbCtrl->rpt_list, bufEntry) ← 追加到链表尾部
brcbCtrl->rpt_count++
if (rpt_list_next == NULL) ← 如果链表之前空了
rpt_list_next = bufEntry; ← 指向新条目(待发送)
缓冲区结构示意:
BRCB 缓冲区链表 (rpt_list):
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Entry#1 │ → │ Entry#2 │ → │ Entry#3 │ → │ Entry#4 │ → NULL
│(最旧) │ │ │ │ │ │(最新) │
│ ID=0001 │ │ ID=0002 │ │ ID=0003 │ │ ID=0004 │
│ data[..] │ │ data[..] │ │ data[..] │ │ data[..] │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
↑
rpt_list_next ─── 指向"下一个待发送"的条目
3. 缓冲区溢出处理
buflist_discard_old()(第 437 行):
while (cur_bufsize > brcb_bufsize || rpt_count > 500)
{
entry = rpt_list; // 取最旧的条目
if (entry == rpt_list_next) // 如果丢弃的是"待发送"条目
{
BufOvfl = TRUE; // 置位溢出标志
rpts_lost_count++; // 丢失计数++
}
buflist_entry_destroy(brcbCtrl, entry); // 释放数据内存,从链表移除
}
触发条件:
cur_bufsize > brcb_bufsize— 总字节数超过配置上限rpt_count > 500— 条目数超过 500(硬限制)
4. 定时发送机制 — 谁触发发送?
4.1 主循环入口
test.c 的 main() 每个周期调用:
while (1) {
iec61850_server_main(); // ← 这个函数内部调用 mvl61850_rpt_service()
usleep(10000); // 10ms 周期
}
scl_srvr.c 中:
void iec61850_server_main()
{
mvl61850_rpt_service(); // ← 服务所有报告控制块
}
4.2 mvl61850_rpt_service() 遍历所有 RCB
// mvl61850_rpt.c:1425
void mvl61850_rpt_service()
{
timeNow = sGetMsTime(); // 取当前时间戳
for (rptCtrl = g_rptCtrl; rptCtrl; ...) // 遍历所有 RCB
{
if (rptCtrl->rcb_type == RCB_TYPE_IEC_BRCB)
mvl61850_brcb_client_service(rptCtrl, &rptCtrl->only_client, timeNow);
else // URCB
mvl61850_urcb_client_service(rptCtrl, &rptCtrl->only_client, timeNow);
if (++num >= 255) break; // 每个周期最多处理 255 个
}
}
4.3 mvl61850_brcb_client_service() — BRCB 服务逻辑
void mvl61850_brcb_client_service(rptCtrl, rptClient, timeNow)
{
// 阶段1: 检查保留时间 (ResvTms) 是否超时
if (rptClient->ResvTmsExpireTime != 0.0)
{
if (timeNow <= ResvTmsExpireTime)
// 客户端重连了 → 清除保留时间
else
// 超时 → 清除 Owner,释放 RCB
}
// 阶段2: 检查定时器和触发条件
mvl61850_rcb_chk_state(rptCtrl, rptClient, timeNow); // ← 关键
// 阶段3: 如果缓冲区有待发送的报告 && 网络栈可用
if (rptClient->netInfo // 客户端已连接
&& RptEna // 报告已使能
&& rpt_list_next != NULL) // 有待发送条目
{
if (mvl_ureq_bufs_avail(netInfo) > 0) // MMS 栈缓冲区可用
{
entry = rpt_list_next;
mvl61850_brcb_rpt_send(rptCtrl, rptClient, entry, &all_seg_done);
if (all_seg_done) {
rpt_list_next = list_get_next(entry); // 指向下一个待发送
SqNumInt16u++; // 序列号++
BufOvfl = FALSE; // 清除溢出标志
}
}
}
}
4.4 mvl61850_rcb_chk_state() — 定时器判决中心
// mvl61850.c:130
void mvl61850_rcb_chk_state(rptCtrl, rptClient, timeNow)
{
if (RptEna || (BRCB && enabled_once))
{
// 判断1: 完整性周期 (IntgPd) 超时?
mvl61850_integrity_timeout(rptClient, timeNow);
// 判断2: 有变化的触发?
if (rptClient->numTrgs > 0) // ← 从 mvlu_rpt_va_change2 中累积的
{
// 如果 BufTim==0 (不缓存) 或者 BufTim 已到期
if (BufTim == 0 || timeNow > buf_time_done)
{
mvlu_rpt_ready(rptClient, RBE); // 决定发送方式
// ─→ BRCB: mvl61850_brcb_rpt_save() (已缓存)
// ─→ 此处: 不会再触发 save,因为已经 save 过了
// numTrgs 在 mvlu_rpt_va_change2 中已递增
// 这里只是决定 "何时发送"
}
}
}
}
4.5 mvlu_rpt_ready() 的分发决策
// mvlu_rpt.c:2683
ST_RET mvlu_rpt_ready(rptClient, rpt_type)
{
switch (rptCtrl->rcb_type)
{
case RCB_TYPE_IEC_BRCB:
// BRCB: 之前已经 save 到缓冲区了
// 这里只是通知 BRCB "可以发送了"?
// 不对... 实际上 mvlu_rpt_va_change2 的 _chk_reason 中
// 当 BufTim=0 或 BufTim 超时时,
// 才会调用 mvlu_rpt_ready → brcb_rpt_save
// 所以 mvlu_rpt_va_change2 中:
// 第一次变化: 启动 BufTim 定时器,设 changed_flags
// 后续变化: 如果 BufTim 策略=立即发送 → 立即触发
// rcb_chk_state 中:
// BufTim 到期 → 调用 mvlu_rpt_ready → brcb_rpt_save
// 然后 brcb_client_service 的 阶段3 → brcb_rpt_send
break;
case RCB_TYPE_IEC_URCB:
mvl61850_urcb_rpt_send(rptCtrl, rptClient, rpt_type);
break;
case RCB_TYPE_UCA:
mvlu_send_report(rptClient, rpt_type);
break;
}
}
5. 完整时间线示例
以下是一个 BRCB 从数据变化到报告发送的完整时间线:
时间轴:
t=0ms 数据点 A 从 0→1
iec61850_put_data()
→ mvlu_rpt_va_change2()
→ _mvlu_rpt_va_chk_reason()
→ numTrgs==0, 启动 BufTim 定时器
→ buf_time_done = now + 200ms (假如 BufTim=200ms)
→ changed_flags[A] = 1, numTrgs=1
t=50ms 数据点 B 从 0→1
→ mvlu_rpt_va_change2()
→ numTrgs>0, changed_flags[B] 已在变化中
→ 如果 buftim_action=SEND_NOW
立即调用 mvlu_rpt_ready()
→ mvl61850_brcb_rpt_save() ← 条目1:包含 (A=1)
→ 重启 BufTim: buf_time_done = now + 200ms
→ changed_flags[A]=1, changed_flags[B]=1, numTrgs=2
t=100ms 数据点 A 从 1→0
→ mvlu_rpt_va_change2()
→ 第二次变化(不同于上次值)
→ changed_flags[A] 已经变化过
→ buftim_action=SEND_NOW
→ mvlu_rpt_ready()
→ mvl61850_brcb_rpt_save() ← 条目2:包含 (A=1, B=1)
→ 重启 BufTim: buf_time_done = now + 200ms
→ changed_flags[A]=1, changed_flags[B]=1, numTrgs=3
t=300ms 主循环 -> mvl61850_rpt_service()
→ mvl61850_rcb_chk_state()
→ numTrgs>0 且 timeNow(300) > buf_time_done(300)
→ mvlu_rpt_ready()
→ mvl61850_brcb_rpt_save() ← 条目3:包含 (A,B)
→ mvl61850_brcb_client_service()
→ rpt_list_next != NULL (条目1)
→ mvl61850_brcb_rpt_send(条目1) ← 编码并发送
→ rpt_list_next = 条目2
t=310ms 主循环 -> mvl61850_rpt_service()
→ brcb_client_service()
→ rpt_list_next = 条目2
→ mvl61850_brcb_rpt_send(条目2) ← 编码并发送
→ rpt_list_next = 条目3
t=320ms brcb_client_service()
→ mvl61850_brcb_rpt_send(条目3) ← 编码并发送
→ rpt_list_next = NULL (全部发完)
6. 关键时序参数总结
| 参数 | 来源 | 作用 |
|---|---|---|
| BufTim | RCB 配置 (ms) | 缓存时间窗口。变化发生后等待 BufTim ms 再生成报告,期间的变化可以合并。0=不缓存 |
| IntgPd | RCB 配置 (ms) | 完整性周期。每隔 IntgPd ms 发送一次全数据集报告,不管数据是否变化 |
| buf_time_done | 运行时计算 | sGetMsTime() + BufTim,第一次变化时设置 |
| next_integ_rpt_time | 运行时计算 | 下一次完整性报告到期时间 |
| TrgOps | RCB 配置 (位图) | 触发选项:dchg(数据变化)/qchg(品质变化)/dupd(数据更新)/integrity(完整性) |
| RptEna | 客户端写入 | 报告使能开关 |
7. BRCB vs URCB 的行为差异对比
| 方面 | BRCB (带缓存) | URCB (不带缓存) |
|---|---|---|
| 数据存储 | 深拷贝到链表缓冲区 | 直接引用实时数据区 |
| 连续变化 | 合并到一帧,间隔≤BufTim | 每变化一次发一帧 |
| 断线重连 | 自动缓存,重连后补发 | 断线期间数据丢失 |
| 报文完整性 | EntryID 编号,BufOvfl 标志 | 无 |
| 发送函数 | brcb_rpt_save → brcb_rpt_send |
urcb_rpt_send (直接编码发送) |
| 典型场景 | 重要遥信、SOE 事件 | 一般遥测、状态量 |
8. 关键代码关系总结
iec61850_put_data2()
→ writedata_*()
→ mvlu_rpt_va_change2() [每次数据变化时触发]
→ _mvlu_rpt_va_chk_reason() [检查 TrgOps 匹配 + BufTim 策略]
→ 记录 changed_flags + reasons_data + numTrgs
→ (若 BufTim 策略=立即发送) mvlu_rpt_ready()
→ mvl61850_brcb_rpt_save() [深拷贝数据到链表]
主循环 (10ms 周期)
→ iec61850_server_main()
→ mvl61850_rpt_service() [遍历所有 RCB]
→ mvl61850_brcb_client_service()
→ mvl61850_rcb_chk_state() [检查 IntgPd 和 BufTim 到期]
→ (IntgPd 到期) mvlu_rpt_ready() → brcb_rpt_save()
→ (BufTim 到期 && numTrgs>0) mvlu_rpt_ready() → brcb_rpt_save()
→ (缓冲区非空) mvl61850_brcb_rpt_send()
→ 组装 MMS 报告报文 (RptID/OptFlds/SqNum/EntryID/时间戳/数据/reason)
→ mvl_info_variables() [MMS 编码 + TCP 发送]
posted on 2026-07-30 18:07 weilai2345 阅读(22) 评论(0) 收藏 举报
浙公网安备 33010602011771号