传输层核心概念:传输层的作用与职责
各位同学,今天我们来聊聊传输层。说实话,很多刚接触UniPro的工程师,最容易把传输层和链路层搞混。我刚开始做MIPI协议栈的时候,也犯过这个错。
传输层在UniPro协议栈里,位置很关键。它夹在应用层和链路层中间,像个翻译官。上层发下来的数据,它要拆包、加头、分段;下层送上来的数据,它要重组、校验、递交。说白了,传输层就是负责端到端可靠传输的那一层。
它的核心职责,我总结为三点:
- 数据分段与重组:上层一个大包,传输层要切成小段,方便链路层发送。反过来,链路层收上来一堆小段,传输层要拼回原来的大包。
- 流量控制:防止发送方太快,把接收方撑死。这个机制叫FC(Flow Control),后面会细讲。
- 错误恢复:链路层如果丢包了,传输层要负责重传。嗯,这里要注意,不是所有丢包都重传,要看配置。
我在项目中遇到过一个问题:有个同事把传输层的重传超时设得太短,结果链路层稍微有点延迟,传输层就疯狂重传,把带宽全浪费了。后来我建议他把超时时间调到链路层最大延迟的两倍,问题就解决了。你想想看,这种坑,光看协议文档是看不出来的。
传输层与链路层的接口
传输层和链路层之间,是通过一组服务原语通信的。这组原语定义在UniPro的L2.5层规范里。说白了,就是传输层调用链路层的接口,链路层把结果返回给传输层。
常用的原语有这么几个:
| 原语名称 | 方向 | 作用 |
|---|---|---|
| L2.5_Data.req | 传输层 → 链路层 | 请求发送一个数据单元 |
| L2.5_Data.ind | 链路层 → 传输层 | 指示收到一个数据单元 |
| L2.5_Credit.req | 传输层 → 链路层 | 更新信用量(流量控制用) |
| L2.5_Credit.ind | 链路层 → 传输层 | 通知信用量变化 |
我个人习惯,在设计接口时,会把信用量更新和数据处理分开。为什么?因为信用量更新是周期性的,而数据发送是事件驱动的。混在一起,代码逻辑容易乱。
这里有个避坑指南:我曾经在实现L2.5接口时,把信用量更新放在数据发送的中断里。结果链路层一忙,中断被阻塞,信用量更新就延迟了,导致发送方误以为接收方没空间,白白浪费了带宽。后来我把信用量更新单独拎出来,用定时器触发,问题就解决了。
传输层数据单元(TPDU)格式
TPDU,全称Transport Protocol Data Unit,是传输层的基本数据单元。每个TPDU包含一个头部和负载。头部固定长度,负载可变。
TPDU头部格式如下:
+--------+--------+--------+--------+--------+--------+--------+--------+
| Ver | Flags | Seq# | Ack# | Len | FC | CRC | ... |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 4 bits| 4 bits| 16 bits| 16 bits| 8 bits | 8 bits | 16 bits| ... |
+--------+--------+--------+--------+--------+--------+--------+--------+
各字段含义:
- Ver:版本号,当前是0x1。我建议你写代码时,先校验这个字段,版本不对直接丢弃,省得后面解析出错。
- Flags:标志位,包括S(起始段)、E(结束段)、R(重传请求)等。嗯,这里要注意,S和E不能同时为1,除非是单段TPDU。
- Seq#:序列号,用于排序和重传。每个TPDU一个唯一序列号,范围0~65535。
- Ack#:确认号,表示接收方期望收到的下一个序列号。说白了,就是告诉发送方:你发到Seq#-1的包我都收到了。
- Len:负载长度,单位是字节。最大255字节,因为字段只有8位。
- FC:流量控制字段,携带信用量信息。这个字段在数据TPDU和专用FC TPDU里都会出现。
- CRC:头部校验和,保护头部不被篡改。负载的校验由链路层负责。
我在项目中遇到过一个问题:有个芯片的传输层实现,把CRC字段算错了。结果链路层收包后,校验头部CRC总是不通过,导致所有包都被丢弃。查了两天才发现,是CRC算法里把填充位也算进去了。你想想看,这种低级错误,光看协议是看不出来的,必须动手调。
重要提示:TPDU的序列号是循环使用的。当Seq#达到65535后,下一个包从0开始。接收方要处理好回绕问题,否则会出现乱序。
个人经验:我建议你在设计接收缓冲区时,至少留出两倍的窗口大小。这样即使发送方重传,接收方也有空间缓存乱序包。我曾经只留了一个窗口大小,结果重传一多,缓冲区就满了,导致死锁。
避坑指南:我曾经在实现TPDU解析时,没有校验Len字段是否超过实际负载长度。结果收到一个恶意包,Len=255但实际负载只有10字节,解析器直接越界访问,导致系统崩溃。后来我加了个长度校验,问题就解决了。
好了,传输层的核心概念就讲到这里。下面我用一张图,把传输层、链路层、TPDU的关系串起来,方便你理解。
这张图很直观:传输层生成TPDU,通过L2.5接口传递给链路层。链路层再把TPDU封装成帧,发送到物理层。反过来,链路层收到帧后,解出TPDU,再通过L2.5接口交给传输层。整个过程,说白了就是一层包一层,各司其职。
好了,传输层的核心概念就讲到这里。下一节我们讲流量控制,那是传输层最复杂的部分,也是面试最爱问的。各位回去把TPDU格式记熟,下节课我要提问。