4、L2CAP层优化:MTU配置、分段与重组、流控机制
L2CAP 层,说白了就是 BLE 协议栈里的“交通调度中心”。
很多人调吞吐量,上来就改 PHY、调连接间隔,结果发现效果有限。为什么?因为数据到了 L2CAP 层被卡住了。我早期做项目时就吃过这个亏——物理层速率已经跑到 2M,但实际吞吐量死活上不去。后来一查,L2CAP MTU 没配,分段策略也不对。
这一章,咱们就把 L2CAP 层的三个关键点讲透:MTU 怎么配、分段重组怎么玩、流控机制怎么调。
4.1 L2CAP MTU 配置——吞吐量的第一道门槛
MTU(Maximum Transmission Unit)决定了单个 L2CAP 数据包能承载多少有效载荷。默认值通常是 23 字节,但实际能配到 247 字节甚至更高。
我个人习惯,在项目初期就把 MTU 协商逻辑写进初始化流程。具体做法是:
- 客户端发起
MTU Exchange Request,带上自己支持的 MTU 值。 - 服务端回复
MTU Exchange Response,取双方支持的最小值。 - 协商完成后,后续所有 L2CAP 数据包都按这个 MTU 值发送。
举个例子,我在一个健康监测设备项目中,把 MTU 从默认的 23 提到 247,吞吐量直接从 8 KB/s 跳到 45 KB/s。嗯,这个提升幅度,你想想看,几乎就是白捡的。
Exchange MTU 请求触发。大部分现代 BLE 芯片都支持。
4.2 分段与重组——大包怎么拆、怎么拼
当应用层数据超过 L2CAP MTU 时,就需要分段。分段后的每个包,在接收端重组回原始数据。
这里有个坑:分段粒度。我见过有人把 500 字节的数据分成 3 个包,每个包 200、200、100。结果接收端重组时,因为包到达顺序不一致,导致重组失败。
分段与重组的核心流程如下:
- 发送端: 检查数据长度 > MTU → 按 MTU 切分 → 添加 L2CAP 头部(包含长度和通道 ID)→ 发送。
- 接收端: 接收分段 → 检查 L2CAP 头部 → 重组缓冲区 → 等待所有分段到达 → 重组完成 → 提交给上层。
这里我建议你关注一下 重组超时。如果某个分段丢失,接收端会一直等,导致缓冲区被占用。我一般设置 100ms 的超时,超时后直接丢弃整个数据包。
4.3 流控机制——别让数据堵在路上
L2CAP 层的流控,说白了就是防止发送端太快,接收端来不及处理。BLE 的 L2CAP 流控主要靠 Credit-Based Flow Control。
这个机制怎么工作的?我简单讲一下:
- 接收端给发送端分配“信用额度”(Credits)。
- 发送端每发一个包,消耗一个 Credit。
- 当 Credits 用完,发送端必须停下来,等接收端补充 Credits。
我在一个数据采集项目中,发现吞吐量波动很大。后来抓包一看,Credits 补充太慢,发送端经常空等。解决办法是:增大初始 Credits 数量,并缩短 Credits 补充的响应时间。
| 参数 | 默认值 | 优化建议 | 说明 |
|---|---|---|---|
| 初始 Credits | 1 | 8 ~ 16 | 增大初始额度,减少等待次数 |
| Credit 补充阈值 | 0 | 50% | 当剩余 Credits 低于 50% 时触发补充 |
| Credit 补充包大小 | 1 | 4 ~ 8 | 一次补充多个 Credits,减少交互次数 |
4.4 本章知识体系
下面这张图,把 L2CAP 层优化的三个核心维度串起来了。你可以对照着看,理解它们之间的关系。
4.5 实战建议
最后,我总结几条实战中可以直接用的建议:
- MTU 协商一定要做。 哪怕只提到 100 字节,也比默认的 23 字节强很多。
- 分段大小固定。 除了最后一个包,其他分段都等于 MTU 值。这样接收端重组逻辑最简单。
- Credits 初始值给大一点。 我一般给 8 或 16,然后根据实际吞吐量微调。
- 抓包验证。 用 Ellisys 或 nRF Sniffer 看看 L2CAP 层的交互,很多问题一眼就能看出来。