如果你是做嵌入式或者工业通信的,大概率听过“黑通道协议”这个说法。我第一次听到这个词的时候,以为是什么加密信道或者神秘隧道,后来做功能安全项目才搞明白,它其实是功能安全通信里一个非常经典的设计思路:把底层传输链路当作不可信的通道,所有安全相关的保障机制全部放到上层自定义实现。所以问题来了:可以自定义黑通道协议吗?
我的答案是:可以,但自定义不是“随便写个私有协议”那么轻松,里面有不少门道。这句话值得展开讲,因为我在实际项目里见过太多把“自定义协议”理解成“画一张帧格式图,然后让两边对上”的情况,结果一到现场就翻车。
这篇文章会把概念、硬约束、帧格式设计、踩坑案例到验证手段整个链路过一遍,主要面向正在做嵌入式通信、工业总线、板间通信,或者对功能安全感兴趣的工程师。如果你只是想在两个开发板之间传几个字节,那看前两章就够;如果要面对执行机构、车辆控制或者工业设备,那后面的章节才是真正值钱的部分。
1. 先搞清楚:你口中的“黑通道协议”是哪个意思
“黑通道”这个词在不同语境下含义差别很大。不先把这个概念理清,后面所有讨论都会跑偏。
1.1 功能安全语境里的Black Channel
功能安全标准族里,IEC 61508是母标准,工业通信领域看IEC 61784-3,汽车领域看ISO 26262。这些标准里都涉及一个“黑通道”模型,英文叫Black Channel。
它的核心逻辑是这样的:一条普通通信链路,比如RS485、CAN、以太网,在承载功能安全数据时,这条底层链路本身不被信任。我们不指望它不出错,而是假设它什么错都可能出:位翻转、丢帧、乱序、插帧、延迟、数据被篡改,全都要当作必然发生的情况来对待。底层链路能提供的任何可靠性机制,在安全模型里都默认不可依赖。所有安全机制必须放在两端设备的上层应用里自己实现,这就是黑通道。
为什么要这样设计?因为底层往往是标准商用硬件,一颗CAN控制器、一个以太网PHY,你不能要求每个芯片都按照功能安全标准重新设计一遍,那成本没边了。与其改造链路,不如在高层做一层“安全包”。
我经常用一个寄快递的类比来解释:快递公司可能把包裹摔了、丢了、送错方向,你唯一能做的就是把它装进抗冲击的箱子,再加防盗锁和签收码。抗冲击箱体、防盗锁、签收码,就是你的安全层。至于快递车走什么路线、中转站用什么分拣系统,那不是你能控制的事。
实际上,IEC 61784-3里定义的很多工业安全通信协议,比如PROFIsafe、CIP Safety、FSoE,本质上都是“黑通道之上自定义安全协议”的标准化版本。思路完全一致:底层用标准总线,上层加安全字段和状态机。
1.2 私有系统里工程师口中的“黑通道”
不过,日常项目里很多人说“黑通道协议”,未必是指功能安全那一套。
我也见过不少工程师把自定义私有协议、裸数据透传、点对点直连通道叫作“黑通道”。这种语境下的意思是:不走公开的标准协议栈,帧格式自己定,收发逻辑自己写,对外部工具和旁观者来说,这个通道就是个黑盒子,不知道里面是怎么组织的。
这两种说法并不冲突,但讨论“自定义”时侧重点完全不同。私有系统里的黑通道,重点在“私有”,你想怎么定就怎么定,只要两个端点能对上就行;功能安全里的黑通道,重点在“安全机制”,你必须证明这套机制能对抗指定的故障集合。
这篇文章两种含义都会覆盖,但我重点讲后者。原因很简单:如果只是两个设备之间传几个字节,你写个带CRC的串口协议就能跑,根本不需要“黑通道”这么大的词。一旦你把协议叫成黑通道,说明你打算认真对待可靠性,那就得按功能安全的思维来设计。
1.3 标准协议这么多,为什么还要自己定义
可能有人会问:标准安全协议那么成熟,为什么还要自己造轮子?
这个问题的答案很现实。第一,标准安全协议的实现和认证成本高,小规模项目根本吃不消。第二,有些场景资源极其有限,一颗8位MCU、2KB RAM,跑不了完整工业以太网协议栈。第三,带宽和实时性要求极端,标准协议帧头帧尾开销太大,把有效吞吐挤没了。第四,硬件形态特殊,自己做的采集板、扩展板之间走SPI或UART,内部链路根本没有现成标准协议可选。
自己做一套针对性的小协议,很多时候不是炫技,而是最省事、最可控的方案。前提是别把它做成“拍脑袋协议”。
2. 自定义之前,先认清这几个硬约束
很多人一提自定义协议,上来就画帧格式图。我的建议是,画图之前先把约束想清楚,否则后面返工成本极高。
2.1 安全机制组合是底线
功能安全通信要求协议能对抗哪些故障?我列一个清单:
- 重复帧:底层把同一帧发了两次,接收方当成两条新指令,可能导致执行机构动作两次
- 丢帧:底层吞掉一帧,控制周期断了一拍,接收方还在用旧数据做决策
- 插入帧:总线上出现干扰或某节点产生一个假帧,接收方当成合法指令执行
- 乱序:帧A和帧B顺序颠倒,接收方执行了过时的B,再执行新的A,逻辑全乱
- 数据损坏:单个比特翻转,温度从25变成225
- 延迟:帧到得太晚,接收方可能已经按超时处理了一次
- 伪装或地址错误:一个节点冒充另一个节点发送数据
应对手段在标准里非常明确,组合起来用:序列号对抗重复和乱序,时间戳和超时对抗延迟与丢帧,源目地址对抗伪装和插入,CRC对抗数据损坏,应答提供确认。这组措施是底线,不是可选项。
自定义协议时当然可以有裁剪空间,但每剪掉一项,你都要能解释清楚“这个故障场景为什么在我的系统里不存在”。比如两个板卡物理直连、中间没有任何第三方总线,那“伪装”的概率确实极低,可以弱化地址验证。但丢帧和损坏是物理链路永远逃不掉的问题,所以CRC和序列号不能省。
2.2 可靠性机制要独立,别把所有鸡蛋放一个篮子里
黑通道的精髓是“不信任底层”。这里有一个容易踩的陷阱:底层链路已经帮你做了一层CRC,上层安全层就不再做CRC了?
不行。
因为黑通道模型认为底层不可信,底层说“我的CRC通过了”这件事本身也不可信。安全层的校验必须独立于链路层,它得有能力发现链路层把错帧当成好帧交上来。所谓独立性,就是安全层的错误检测逻辑不能依赖底层逻辑的正确性。
实际工程里还有一类更隐蔽的问题:安全层的状态变量和链路层的错误计数定义在同一个结构体里,中断服务程序里两边都去改,最终一次普通丢包就会导致安全层误判重启。我的建议是,链路层和安全层使用不同的状态机、不同的错误计数器、不同的内存区域,如果可能,把安全层放到独立的中断优先级或独立任务里,杜绝共享变量。
2.3 性能和实时性:安全机制是有代价的
每帧多4字节CRC、多2字节序列号,看起来不多,但别忽略计算和状态维护的开销。
我给你算一笔账。假设底层是115200bps的UART,一个字节传输时间约87微秒,一帧40字节就要约3.5毫秒。如果一个控制周期是5毫秒,链路利用率已经到70%以上了。这时候再塞一个软件逐位计算的CRC32,在低端MCU上可能吃掉几百微秒甚至更多,极可能打乱整周期时序。换成查表法CRC,开销能降到几十微秒,如果硬件有CRC外设,那就基本不占CPU。
超时阈值同样要算。举个例子:发送周期T=10ms,最大抖动J=4ms,那么两个合法连续帧之间的最大间隔是14ms。超时阈值设12ms,正常抖动也会被误判成丢帧;设20ms,链路已经断了20ms你才发现。阈值不是拍脑袋定的,而是T、J和系统安全响应时间三者妥协的结果。做设计前,先把通信拓扑、最大节点数、最小帧周期这些参数写清楚,再定字段宽度和超时值。
2.4 可诊断性:黑通道不能变成“盲通道”
自定义协议最怕什么?最怕出了问题没人看得懂。黑通道是“不信任底层”,不是“连上层也看不见”。
我强烈建议在协议里预留错误计数器和诊断机制,至少涵盖这几类信息:收发包总数、CRC错误包数、序列号错误数、超时次数、最后错误类型、最后错误帧的关键片段。
现场最痛苦的情况是系统黑屏、没有任何日志、全靠工程师用万用表猜。如果你在设计阶段就布置好诊断帧和错误上报,问题定位时间可以从几天缩到几个小时。有人觉得诊断字段是累赘,等你半夜被叫去现场解决偶发故障时,就会明白这些字段是你的救命稻草。
3. 手把手设计一个可自定义的黑通道协议:从帧格式到代码骨架
约束讲清楚了,下面动手。我用一个典型的板间通信场景做例子:两块MCU通过UART连接,一主一从,主机发指令和参数,从机回状态和测量值,要求10ms周期、最多百字节数据、CRC校验、序列号防丢帧和重复帧,主机还能通过诊断接口看链路质量。
3.1 第一步:定义帧格式
帧格式是协议的地基。我建议把固定头部和可变数据明确分开,方便接收端先收固定头,再按长度字段收数据。一个比较实用的格式如下:
| 字段 | 长度 | 说明 |
|---|---|---|
| 同步字1 | 1字节 | 0xAA |
| 同步字2 | 1字节 | 0x55 |
| 帧长度 | 1字节 | 数据段长度,最大248 |
| 源地址 | 1字节 | 发送方编号 |
| 目的地址 | 1字节 | 接收方编号 |
| 帧类型 | 1字节 | 数据帧、应答帧、心跳帧、诊断帧 |
| 序列号高字节 | 1字节 | 发送方递增计数 |
| 序列号低字节 | 1字节 | 回绕处理用 |
| 数据段 | 0~248字节 | 业务数据 |
| CRC32 | 4字节 | 从同步字到数据段末尾的校验值 |
同步字用0xAA55双字节而不是单字节0xAA,是为了降低误同步概率。在UART这种字节流上,单字节同步字很容易在任意数据里冒出来。长度字段的最大值和缓冲区大小要匹配,我这里定248字节,因为1字节无符号最大值是255,减去8字节固定头部和4字节CRC正好是243字节余量,留几个字节给后续扩展。
地址字段在只有两个节点时可以固定成常数,但保留它成本很低,建议保留。多点总线或者将来扩展从站时,这个字段能直接救命。
帧类型字段需要特别强调一点:安全数据帧和诊断帧必须能区分开。我见过有人把诊断命令和数据帧混在一起,结果调试时一条诊断命令把现场执行机构给触发了。诊断帧的接收处理路径必须独立,收到诊断帧只做记录和上报,绝不进入控制逻辑。
3.2 第二步:CRC选型和多项式选择
CRC选型不是随便拿个多项式就开干。常见选择是这三档:
- CRC-8:适合长度很短、误码率要求不高的场合,比如传感器节点传温度。用之前要想清楚,残余错误概率能否接受
- CRC-16/CCITT,多项式0x1021:覆盖几百字节足够,是低成本MCU上最常见的方案,性能开销适中
- CRC-32,多项式0x04C11DB7:适合较长帧,或者对残余错误概率有更严要求的系统,开销相对高
安全相关场景我一般起步就是CRC-16,数据较长或者系统风险较高就直接上CRC-32。但这里有个更重要的提醒:CRC能覆盖多长的数据、多项式是否是标准推荐值、初始值(Init)、输出异或值(XOROUT)、输入输出是否需要反射(ReflectIn/ReflectOut),这些参数必须全部固定下来写进协议文档。
我在项目里见过一个极其隐蔽的低级问题:上位机用某个代码生成器,下位机用另一个库,两边对同一个包算出来的CRC完全不同。原因不是多项式错了,而是初始值或反射配置不一致。排查了整整一天,最后一对照参数才发现CRC算法的五个参数没有同步。这种问题在设计阶段就能避免——协议文档里单独写一节“CRC参数定义”,把以下参数列清楚:
- 多项式(Poly)
- 初始值(Init)
- 输出异或值(XorOut)
- 输入反射(RefIn)
- 输出反射(RefOut)
另外,CRC计算范围要从同步字开始一直到数据段末尾,只覆盖数据段意义不大,因为头部出错也可能让接收方把错帧当成有效帧。还有一些系统喜欢对CRC本身做异或,这些都可以,但参数一定要写清楚。
3.3 第三步:序列号、心跳和超时
序列号和心跳是自定义协议里对抗“时间维度故障”的核心组合。
序列号由发送方维护,每发一个安全数据帧就递增1。接收方缓存上一个有效帧的序列号,校验新帧是否满足“下一个”关系。这里有个关键点:判断“下一个”不能写cur > prev,因为序列号溢出回绕时大小比较会失效。正确写法是利用无符号整数环绕特性:
static int seq_is_next(uint16_t prev, uint16_t cur) { return (uint16_t)(cur - prev) == 1; }0xFFFF的下一个就是0x0000,无符号减法自动完成回绕,这条判断天然正确。我之前见过有人用cur == prev + 1,prev为0xFFFF时不溢出报错就是误判,影响很大。
心跳帧的作用是链路空闲时确认对端还活着。比如主从系统平时只有收到请求才应答,从机突然故障后主机可能要很久才能发现。加一个心跳机制,比如空闲超过2秒就发一帧心跳,超时阈值设为心跳周期的两倍以上,链路异常就能被快速检测到。心跳帧也要带序列号和CRC,不能让接收方把过时的心跳当成当前状态。
超时阈值的设计前面已经说过,要基于发送周期和抖动来定,不能拍脑袋。设小了误报,设大了故障影响时间变长,这个参数应当在需求阶段就和系统安全响应时间一起讨论。
3.4 第四步:应答与重传
应答与重传不是所有场景都要用,分开看。
如果是主从问答式指令,比如主机发“打开阀门”“切换到自动模式”,这类命令必须可靠到达。常见做法是主机等待从机应答,超过N毫秒未收到就重发,重发次数超过阈值就报故障。但这里有个非常容易踩的坑:从机可能已经执行了指令,只是应答帧在总线上丢了。主机重发后,从机如果再次执行,等于重复了一次动作。这绝对不能接受。
解决方案是重发帧必须携带相同的序列号,从机根据序列号判断“这帧我处理过了”,只重新发回应答,不再执行。本质上序列号在这里承担了“幂等性检查”的职责。
如果是周期性的数据上报,比如传感器每10ms上报一次温度,就不需要逐帧应答和重传。接收方用序列号检查连续性,发现丢帧后按错误计数或超时处理,下一周期自然会来新数据。强行对周期帧做应答重传,反而会把旧数据反复塞给应用层,掩盖链路故障的真实严重程度。
3.5 第五步:一个最小可运行的接收校验骨架
下面这段代码演示接收校验最核心的一段逻辑。我故意不贴完整协议栈,因为不同项目的状态机和业务逻辑差异太大,贴出来反而容易误导人。但校验顺序和边界判断是通用经验,可以直接抄。
#define FRAME_SYNC0_IDX 0 #define FRAME_SYNC1_IDX 1 #define FRAME_LEN_IDX 2 #define FRAME_SRC_IDX 3 #define FRAME_DST_IDX 4 #define FRAME_TYPE_IDX 5 #define FRAME_SEQ_H_IDX 6 #define FRAME_SEQ_L_IDX 7 #define FRAME_DATA_IDX 8 static uint8_t rx_buf[256]; static uint16_t rx_len; uint8_t black_channel_validate(const uint8_t *buf, uint16_t buf_size, uint16_t *out_seq, uint8_t *out_src) { uint16_t len; uint32_t calc_crc; if (buf_size < 12) return 0; // 至少:同步字+长度+地址+类型+序列号+CRC if (buf[FRAME_SYNC0_IDX] != 0xAA) return 0; if (buf[FRAME_SYNC1_IDX] != 0x55) return 0; len = buf[FRAME_LEN_IDX]; if (len > 248) return 0; if (buf_size < 8u + len + 4u) return 0; // 8字节头部 + 数据 + 4字节CRC calc_crc = crc32_bytes(buf, 8u + len); // 从同步字一直算到数据末 if (calc_crc != read_u32_le(buf + 8u + len)) return 0; *out_seq = (uint16_t)buf[FRAME_SEQ_H_IDX] << 8 | buf[FRAME_SEQ_L_IDX]; *out_src = buf[FRAME_SRC_IDX]; return 1; }这段代码有几个细节值得说。
第一,我不用结构体指针强转。结构体成员在编译器里会对齐,比如2字节成员会按2字节边界对齐,结构体里就会插入填充字节。而从串口收到的是一串紧凑字节流,直接强转会导致所有字段错位。按字节索引最稳妥,也方便以后扩展帧格式。
第二,判CRC之前必须先检查长度字段和缓冲区长度。如果不检查就访问buf后面的数据,短帧会越界,产生“找不到原因的硬件异常”。这类问题一旦发生,调试器都很难定位,因为越界写可能污染内存里的任何东西。
第三,CRC计算覆盖8字节头部加数据段,CRC字段本身不参与计算。帧尾的4字节读取时要统一大小端方式,我这里写了read_u32_le,具体用大端还是小端,协议文档里定死并保持一致就行。
接收端在CRC校验通过后,还要把帧交给安全层状态机:比较序列号是否连续、时间戳是否在有效窗口内、帧类型是否允许在当前状态下接收,全部通过之后才能提交给应用层。这个状态机的代码按项目不同写法差异很大,但核心逻辑就是我前面讲的序列号加超时。
4. 自定义过程中的经典错误与排查思路
下面这些坑我不止一次踩过,也看同行踩过。每一个都是从现场加班换回来的教训。
4.1 只加CRC不防重复帧
早年我做过一个项目,传感器通过RS485接控制器,传感器每100ms上报一次温度值,控制器对CRC校验通过的数据直接采信。刚开始联调一切正常,后来现场偶发“温度瞬间跳回旧值再跳回新值”,查了很久才发现是总线竞争导致某帧延迟,延迟帧携带的是旧数据,CRC完全合法。
问题根因就是只有CRC没有序列号。CRC只能证明数据没有被篡改,不能证明数据是“当前时刻”的数据。接收方看到CRC合法就当成新数据,于是温度表跳回了旧值。加上一个2字节序列号并要求连续性后,问题彻底消失。
这类问题最难的地方在于它不是每次都能复现,而是低概率偶发。没有长时间的抓包或者故障注入,你根本看不到那一帧。所以我现在做任何自定义协议,第一件事就是把序列号设计进帧格式里,不管对方说“链路很可靠”还是“就两个设备直连”。
4.2 把链路层、安全层和应用层搅在一起
见过不少“能跑就行”的代码:串口接收中断里直接解析业务帧,同时改几个全局变量控制电机。表面看省事,等出了问题,你连错误发生在哪一层都判断不了。
我的做法是严格分层:
- 物理层:串口、CAN、以太网的初始化,底层收发中断,缓冲管理
- 链路层:同步字检测、帧长校验、帧边界解析、CRC计算
- 安全层:序列号检查、超时判断、心跳、应答处理、错误计数、安全状态切换
- 应用层:具体指令解析和业务逻辑
每一层有独立的入口和出口,层间用结构体传递解析结果。排查问题的时候,先看链路层错误计数是不是在涨,再看安全层有没有报超时,最后才怀疑应用逻辑。这样组织代码可能文件多一点,但现场救急效率高一个量级。
4.3 忽略故障注入测试
自定义协议写完,最常见的做法是“正常通信测试”:收发都对,就宣布完成。但正常通信测不出协议在异常场景下的行为。
链路会丢包、会产生坏帧、会乱序、会延迟,你的协议设计目的就是让系统在异常下仍然安全。不做故障注入测试,等于只测了晴天路况,没测冰雪路面。
我验证自定义协议,至少跑一轮故障注入:随机丢帧、随机翻转比特、重复发旧帧、打乱帧顺序、人为拔线、注入延迟。每轮都要记录接收方状态和错误计数。协议到底行不行,不是靠“感觉可靠”,是靠这些数据说话。
4.4 一个完整的排查链路实例
最后讲一个印象最深的调试经历。
一个双板卡系统,主卡每5ms发一帧控制数据,从卡执行动作并回状态应答。联调时从卡偶尔重启,概率极低,而且重启后自己能恢复。当时先怀疑看门狗配置,调了参数没有效果;又怀疑电源不稳,用示波器抓电源纹波,也正常;再怀疑内存踩踏,开了编译器的保护机制还是没找到问题。
后来用逻辑分析仪连续抓了很长时间,终于看到异常帧:总线上出现一帧同步字和长度都正确、但数据区是“半新半旧”的帧,CRC自然校验失败,被接收方丢弃。问题根因是发送端的DMA还没有把一帧完全发出,缓冲区就被下一帧数据覆盖,导致发出去的变成“缝合怪”。
代码里当时没有独立的错误计数,底层把CRC错误当成普通丢包处理,连续丢包达到某个阈值后触发了从卡的安全重启逻辑。后来我给链路层加了一个独立的CRC错误计数器和DMA忙状态检查,又在协议里补充了诊断帧,再遇到类似异常时,现场工程师看一眼错误码就能知道是发送端还是接收端的问题。
这个案例说明三件事:一是帧格式必须有诊断途径,否则偶发异常根本无从查起;二是CRC错误不等于可以忽略的噪声,它可能是更严重问题的症状;三是发送端DMA和缓冲区的并发管理,往往是自定义协议最容易翻车的地方。
5. 验证与测试方案:怎么证明你的自定义协议“靠谱”
协议设计得再漂亮,最终要落到“能不能证明它可靠”。这一章讲我常用的验证手段。
5.1 故障注入测试矩阵
先写测试用例再写代码,还是先写代码再补测试?我的习惯是协议设计阶段就把故障注入矩阵写出来。下面这个表是我常用的模板,可以直接抄去用:
| 故障类型 | 注入方式 | 预期行为 |
|---|---|---|
| 单比特翻转 | 抓包后改写数据区某个比特,CRC保持不变 | 接收方CRC校验失败,丢弃 |
| 整帧丢失 | 随机丢弃10%的帧 | 序列号跳变,触发连续丢帧计数或超时 |
| 帧重复发送 | 把上一帧连续发送两次 | 接收方检测到序列号重复,丢弃重复帧 |
| 帧乱序 | 交换相邻两个帧的发送顺序 | 接收方检测到序列号不连续,拒绝乱序帧 |
| 链路断开 | 物理拔线或禁用收发器 | 心跳超时,进入安全状态 |
| 注入延迟 | 人为把某一帧延迟到超时阈值之后 | 接收方判定超时,丢弃延迟帧 |
| 伪装节点 | 用另一个节点伪造源地址发送 | 接收方根据地址字段和不信任原则,拒绝或告警 |
这个矩阵的价值有两点:一是每个故障类型都有对应的预期行为,写代码时可以据此设计测试用例;二是测试记录可以直接作为功能安全评估的证据。如果将来要做审核,审核员一定会问“你怎么证明序列号能防重复帧”,这时候拿出故障注入记录,比任何口头解释都有说服力。
5.2 工具选型
测试工具按投入成本从低到高排序:
- 串口助手:只能看应用层数据对不对,看不到协议层细节,适合冒烟测试
- 逻辑分析仪和示波器:能看物理波形和帧边界,适合排查发送时序、DMA异常,我前面那个“缝合怪帧”就是逻辑分析仪抓到的
- CAN分析仪或总线分析仪:如果黑通道底层是CAN,能直接看链路层ID、DLC和错误帧
- Wireshark加Lua脚本:如果协议跑在以太网或UART转以太网上,写一个几十行的Lua dissector,把字节流解析成协议字段,观察序列号、CRC、错误计数和延迟,效率极高
很多人忽略Wireshark的自定义解包能力。其实花半小时学一下Lua dissector怎么写,对字段型协议来说回报非常大。尤其是数据量大的时候,肉眼对十六进制字节流能看崩溃,而Wireshark能把每个字段直接标出来,一眼就能看出“这帧序列号为什么跳了”。
5.3 自定义协议能达到什么安全等级
很多人问我:自己定义的协议能不能达到SIL3那种等级?
严谨的回答是:协议本身没有等级,等级属于整个安全功能。你需要评估的是采用了哪些安全机制、覆盖了哪些故障模式、残余失效率有多高,并且由独立审核机构来认定。IEC 61784-3里给出的安全通信层,比如PROFIsafe,是经过验证的实现模板,它有完整的故障分析和安全案例做支撑。
你完全自定义一个协议,理论上也可能达到同等的技术能力,但要把它认证到某个SIL等级,审核成本非常高。第三方评估机构会看你的FMEDA、故障树、安全案例、测试覆盖率等一整套文档,工作量比写协议本身大得多。
如果你的行业有明确的功能安全法规要求,比如机器安全、汽车电子,我的建议是优先采用标准安全通信协议,不要在认证这件事上给自己挖坑。如果你做的是内部系统,没有外部认证要求,但依然希望系统更可靠,那自定义黑通道协议是一条很务实的路径。前提是至少覆盖前面说的故障注入矩阵和诊断要求,并且在设计文档里把每一项安全机制的用途写清楚。
最后说说我个人的判断。回到最初的问题:可以自定义黑通道协议吗?可以做,但动手之前,你必须先回答五个问题:
- 你的系统最坏情况下,允许多少毫秒没有新数据?
- 底层链路最坏情况下的丢帧率和误码率大概是多少?
- 重复帧、乱序帧、滞后帧分别会造成什么后果?
- 出问题之后,现场工程师能不能靠错误码定位到具体某一层?
- 这套协议有没有经过故障注入验证?
如果这五个问题的答案都能清楚写下来,协议骨架其实已经在你脑子里成型了。协议设计最难的从来不是画帧格式,而是你在设计时有没有把可能发生的失效都想清楚。自定义是一种自由,但自由要用工程纪律来换。