2、BLE协议栈基础:物理层、链路层、L2CAP、ATT/GATT 层次结构
做BLE吞吐测试,你得先搞清楚协议栈长什么样。
很多人一上来就调参数、改MTU,结果吞吐死活上不去。为什么?因为他不理解每一层在干什么。我刚开始做蓝牙开发时也犯过这毛病,后来被一个老工程师点醒——「你不懂协议栈,你调什么参数?」
好,今天我们就一层层剥开BLE协议栈。从最底下的物理层,一直看到最上面的应用层。
核心观点:BLE吞吐的瓶颈,往往不在某一层,而在层与层之间的配合。你只有看懂每一层的职责,才知道该从哪里下手优化。
2.1 整体架构:四层模型
BLE协议栈分四层,从下往上分别是:
- 物理层(PHY)—— 负责发比特流,管频率、调制、速率
- 链路层(LL)—— 负责连接管理、重传、加密
- L2CAP —— 负责数据分包重组、通道复用
- ATT/GATT —— 负责数据读写、服务发现
说白了,数据从应用层往下走,每层加点头部信息,到物理层变成无线电波飞出去。对面收到后,再一层层剥开,拿到原始数据。
我画了张图,帮你直观理解这个结构:
我的经验:吞吐测试时,我习惯先看L2CAP和链路层。这两层是数据包大小的主要限制点。物理层速率再高,如果L2CAP分包不合理,吞吐照样上不去。
2.2 物理层(PHY)—— 最底层的速度基础
物理层负责把比特变成无线电波。BLE在2.4GHz频段工作,和Wi-Fi、Zigbee挤在一起。
BLE物理层有三种模式:
| PHY模式 | 原始速率 | 有效吞吐(估算) | 适用场景 |
|---|---|---|---|
| 1M PHY | 1 Mbps | ~700 kbps | 兼容性最好,默认模式 |
| 2M PHY | 2 Mbps | ~1.4 Mbps | 高吞吐场景,BLE 5.0引入 |
| LR PHY (长距离) | 125/500 kbps | ~80-350 kbps | 远距离、低速率 |
你想想看,2M PHY的原始速率是1M的两倍,但实际吞吐能翻倍吗?不一定。因为协议开销还在那里。
注意:我曾经在一个项目中,把PHY从1M切换到2M,结果吞吐只提升了40%。为什么?因为链路层的连接间隔没调,L2CAP的MTU也没改。物理层只是管道,管道粗了,但水龙头没拧大,水流还是上不去。
2.3 链路层(LL)—— 连接的心脏
链路层是BLE协议栈里最复杂的一层。它管三件事:
- 连接管理—— 建立连接、断开连接、调整连接参数
- 数据重传—— 丢包了自动重发,保证可靠性
- 加密—— AES-128加密,保证数据安全
链路层的工作方式是「事件驱动」。主设备和从设备约定好时间点,到了时间就交换数据。这个时间点叫连接事件(Connection Event)。
连接事件有两个关键参数:
| 参数 | 含义 | 对吞吐的影响 |
|---|---|---|
| Connection Interval | 两个连接事件之间的间隔 | 间隔越小,吞吐越高,但功耗也高 |
| Slave Latency | 从设备可以跳过的事件数 | 跳过的越多,吞吐越低,但省电 |
举个例子。连接间隔设成7.5ms(最小值),每个事件能发4个包(LL Data Length Extension支持)。那么一秒钟大概能发:
1000ms / 7.5ms ≈ 133 个事件
每个事件 4 个包
总共 ≈ 532 个包/秒
如果每个包能带251字节数据,那理论吞吐就是:
532 × 251 × 8 ≈ 1.07 Mbps
嗯,这里要注意。这只是链路层的理论值。到了L2CAP和ATT层,还要再打折扣。
避坑指南:我曾经调吞吐时,把连接间隔设到最小值,结果设备频繁断连。后来发现是射频环境太差,重传太多,链路层忙不过来了。所以连接间隔不是越小越好,得看实际环境。
2.4 L2CAP —— 数据的分包与重组
L2CAP在链路层之上,负责两件事:
- 数据分包—— 把上层的大数据包切成链路层能处理的小包
- 通道复用—— 多个应用可以共用同一个连接
L2CAP有个重要概念叫MTU(Maximum Transmission Unit)。它决定了上层一次能发多少数据。
BLE 4.2之前,L2CAP的MTU默认是23字节。你没看错,23字节。去掉3字节的ATT头部,实际有效数据只有20字节。这就是为什么老版本的BLE吞吐那么低。
BLE 4.2引入了LE Data Length Extension,把链路层的数据包从27字节扩展到了251字节。L2CAP的MTU也跟着涨到了247字节左右。
到了BLE 5.0,又引入了LE 2M PHY,速率翻倍。但L2CAP的MTU没变,还是247字节。
| BLE版本 | 链路层包大小 | L2CAP MTU | 有效数据(ATT层) |
|---|---|---|---|
| 4.0 / 4.1 | 27 字节 | 23 字节 | 20 字节 |
| 4.2 | 251 字节 | 247 字节 | 244 字节 |
| 5.0+ | 251 字节 | 247 字节 | 244 字节 |
关键点:做吞吐测试时,第一件事就是确认L2CAP的MTU。如果MTU只有23字节,你就算用2M PHY,吞吐也上不去。我见过太多人在这上面踩坑了。
2.5 ATT/GATT —— 应用层的读写接口
ATT(Attribute Protocol)定义了数据的读写方式。GATT(Generic Attribute Profile)定义了数据的组织方式。
说白了,ATT是「怎么读写」,GATT是「读写什么」。
ATT有6种操作:
- Request / Response —— 一问一答,可靠但慢
- Command —— 只发不问,快但不可靠
- Notification —— 从设备主动推数据,不需要确认
- Indication / Confirm —— 从设备主动推数据,需要确认
吞吐测试时,我们一般用Notification。为什么?因为它不需要确认,可以连续发。Indication要等确认,效率低一半。
GATT把数据组织成Service和Characteristic。一个Service包含多个Characteristic,每个Characteristic有一个Value。我们读写的就是这个Value。
我的习惯:做吞吐测试时,我会专门定义一个「吞吐测试Service」,里面放一个「大块数据Characteristic」。然后用Notification往里面灌数据。这样能排除其他服务的干扰,只看纯吞吐。
2.6 各层对吞吐的影响总结
好,我们把四层串起来,看看每一层对吞吐的影响:
| 层次 | 影响吞吐的关键因素 | 优化方向 |
|---|---|---|
| 物理层 | PHY模式(1M/2M/LR) | 切换到2M PHY |
| 链路层 | 连接间隔、Data Length | 缩小连接间隔,启用DLE |
| L2CAP | MTU大小 | 协商MTU到最大值 |
| ATT/GATT | 操作类型(Notification vs Indication) | 使用Notification,避免等待确认 |
你看,每一层都有瓶颈。只优化一层,效果有限。必须四层一起调,才能把吞吐拉满。
再次提醒:我曾经帮一个客户调吞吐,他坚持说「我PHY已经是2M了,为什么吞吐还是上不去?」我一看代码,L2CAP的MTU还是23字节。这就是典型的「只知其一,不知其二」。协议栈是整体,不是单点。