4. 传输层帧结构详解:帧头(Frame Header)字段解析、帧类型(Data/Control/Management)、帧序列号机制
好,咱们今天来啃一块硬骨头——传输层的帧结构。
说实话,我刚接触UniPro那会儿,看着帧头那一堆字段,头都大了。后来在项目中调了整整两周的协议栈,才把这些字段的脾气摸清楚。今天我把这些经验掰开揉碎了讲给你听。
4.1 帧头(Frame Header)字段解析
传输层的帧头,说白了就是给数据包贴的一张“快递单”。这张单子上写了:谁发的、发给谁、发的是什么类型的东西、东西有多长、需不需要回执等等。
帧头的固定长度是8个字节,结构如下:
| 位域 | 宽度(bit) | 说明 |
|---|---|---|
| Frame Type | 2 | 帧类型:00=Data,01=Control,10=Management |
| Sequence Number | 12 | 帧序列号,范围0~4095 |
| CPort ID | 4 | 通信端口标识,最多16个CPort |
| Device ID | 8 | 目标设备ID,用于多设备互联 |
| Length | 12 | 有效载荷长度,单位是字节 |
| Flags | 4 | 控制标志位,如ACK请求、流控等 |
| CRC | 8 | 帧头校验,保护帧头完整性 |
嗯,这里要注意:CRC只保护帧头,不保护载荷。载荷的完整性由链路层的CRC来保证。我见过有人把这两层校验搞混,结果调试时怎么都对不上——你想想看,帧头CRC过了,载荷却坏了,那肯定是链路层的问题。
核心要点:帧头8字节,每个bit都有用。设计上追求极致的紧凑,因为UniPro的物理层带宽很宝贵。
4.2 帧类型:Data / Control / Management
UniPro传输层定义了三种帧类型。为什么是三种?说白了,就是数据面、控制面、管理面三权分立。
4.2.1 Data Frame(数据帧)
数据帧是干活的主力。它承载用户数据,比如摄像头传过来的图像数据、Modem发过来的IP包。数据帧的特点是:量大、要求高吞吐、允许丢包重传。
数据帧的序列号机制最复杂,后面我会细讲。
4.2.2 Control Frame(控制帧)
控制帧是用来管理数据流的。常见的控制帧包括:
- ACK帧:告诉发送方“我收到了,继续发”
- NACK帧:告诉发送方“丢包了,重传吧”
- FC帧(流控帧):接收方说“我快满了,你慢点发”
控制帧的序列号是独立编号的,和数据帧互不干扰。我记得在项目中遇到过一个问题:数据帧和控制帧共用序列号空间,结果控制帧丢了,数据帧也跟着乱套。后来改成独立编号,世界清静了。
4.2.3 Management Frame(管理帧)
管理帧是“大佬”级别的。它负责链路配置、设备发现、电源管理等全局事务。管理帧的优先级最高,但数量最少。
管理帧没有序列号机制——因为它不需要重传。你想想看,配置链路这种事,一次没配好,整个链路都起不来,重传也没意义。
我的经验:调试时优先抓管理帧。如果管理帧交互正常,说明物理层和链路层基本没问题。剩下的就是数据帧和控制帧的配合问题了。
4.3 帧序列号机制
序列号机制,是传输层最核心的设计之一。它解决了三个问题:
- 丢包检测:接收方发现序列号跳了,就知道丢了包
- 乱序重排:接收方按序列号把数据排好,再交给上层
- 流控配合:发送方根据ACK/FC帧调整发送速率
4.3.1 序列号空间
序列号是12bit的,范围0~4095。为什么是4096?因为2的12次方。这个数字不是随便选的——它要保证在重传超时之前,序列号不会回绕。
我算过一笔账:假设链路速率是2.9Gbps,一个数据帧最大载荷是4KB,那么4096个帧能覆盖大约5ms的数据量。而UniPro的重传超时一般是1~2ms,所以4096的序列号空间是够用的。
4.3.2 序列号分配规则
发送方每发一个数据帧,序列号加1。控制帧的序列号是独立分配的,从0开始,每发一个控制帧加1。
这里有个坑:重传帧的序列号不变。也就是说,如果第100号帧丢了,重传时还是用100号。接收方收到重复的100号帧,直接丢弃——这叫“去重”。
注意:我曾经在项目中遇到过一个bug:发送方重传时把序列号改了,结果接收方以为来了新数据,导致数据重复。排查了两天才找到原因。所以记住:重传不改变序列号,这是铁律。
4.3.3 序列号与窗口机制
UniPro传输层使用滑动窗口来控制流量。窗口大小决定了发送方可以连续发多少帧而不等ACK。
窗口大小是动态调整的。接收方通过FC帧告诉发送方“我还有多少缓冲区”。发送方根据这个信息调整窗口。
举个例子:
- 窗口大小=16,发送方可以连续发16帧
- 收到ACK后,窗口向前滑动
- 如果收到NACK,窗口停止滑动,开始重传
嗯,这个机制有点像高速公路上的限速牌——路况好就开快点,路况差就慢点。
4.4 帧结构总览图
下面这张图,是我自己画的一个帧结构总览。它把帧头、帧类型、序列号机制串在了一起,方便你理解整体逻辑。
4.5 实际项目中的避坑指南
最后,分享几个我在项目中踩过的坑,希望能帮你少走弯路。
避坑1:帧头CRC校验不能省
我曾经为了省几个时钟周期,把帧头CRC校验跳过了。结果呢?帧类型字段被误判,数据帧被当成控制帧处理,整个链路直接挂死。后来老老实实加回来,再也不敢偷懒了。
避坑2:序列号回绕要处理好
序列号从4095回到0的时候,接收方要能正确判断。我见过一个实现:用“序列号差值”来判断新旧,结果回绕时差值变成负数,逻辑全乱。正确的做法是用模运算或者环形缓冲区。
避坑3:控制帧的优先级
控制帧虽然数据量小,但优先级一定要高。因为ACK/NACK直接影响数据帧的发送节奏。如果控制帧被数据帧堵住了,整个链路的吞吐量会断崖式下跌。
好了,帧结构这块就讲到这里。记住:帧头是骨架,帧类型是灵魂,序列号是秩序。三者缺一不可。