3. ATT PDU 与吞吐量:数据包才是性能的命门
聊 GATT 性能优化,绕不开 ATT 层。说白了,GATT 是应用层的“规矩”,而 ATT 才是真正在空口上跑数据的“车”。车跑得快不快,取决于车的结构——也就是 ATT PDU。
我刚开始做 BLE 项目时,总觉得上层逻辑写好了就行。直到有一次,产品在产线上测吞吐量,死活差 30%。折腾了两天,最后发现是 ATT MTU 没配对。嗯,从那以后我再也不敢小看 PDU 格式了。
3.1 ATT PDU 的基本结构
每个 ATT PDU 都长一个样,结构很固定:
+--------+--------+------------------+
| 操作码 | 句柄 | 有效载荷 |
| (1字节) | (2字节) | (0-N字节) |
+--------+--------+------------------+
操作码告诉你这是个什么操作——读、写、通知、指示,还是别的。句柄就是你要操作的属性在 GATT 表里的索引。有效载荷嘛,就是真正要传的数据。
我个人习惯把 ATT PDU 想象成一个信封:操作码是信封上的“业务类型”,句柄是收件人地址,有效载荷才是信的内容。你想想看,如果信封本身太大,信的内容自然就少了。
核心要点:ATT PDU 的传输效率 = 有效载荷长度 / 总 PDU 长度。这个比值越高,吞吐量越大。
3.2 四种核心操作:Read / Write / Notify / Indicate
这四种操作是 GATT 通信的基石。我按使用频率和性能影响排个序:
| 操作类型 | 操作码 | 是否需要应答 | 典型场景 | 性能影响 |
|---|---|---|---|---|
| Read Request / Response | 0x0A / 0x0B | 是 | 读取传感器数据 | 中等,一来一回 |
| Write Request / Response | 0x12 / 0x13 | 是 | 配置参数 | 中等,需等待应答 |
| Write Command | 0x52 | 否 | 批量数据下发 | 高,无应答开销 |
| Notify | 0x1B | 否 | 实时数据推送 | 最高,无应答 |
| Indicate / Confirm | 0x1D / 0x1E | 是 | 关键事件上报 | 低,需等待确认 |
看到没?Notify 和 Write Command 不需要应答,所以吞吐量最高。Indicate 虽然也是推送,但需要对方回一个 Confirm,性能就下来了。
我在项目中遇到过一个问题:产品经理要求用 Indicate 上报心率数据,说是“确保服务器收到”。结果一测,每秒只能传 10 包,而 Notify 能到 60 包。后来我们改成了 Notify + 应用层确认,性能翻了 6 倍。
我的建议:能上 Notify 就别用 Indicate。如果担心丢包,在应用层做超时重传,比 ATT 层的 Confirm 机制灵活得多。
3.3 PDU 长度与传输效率
ATT PDU 能传多长?这取决于两个东西:
- ATT MTU:ATT 层能处理的最大 PDU 长度。默认是 23 字节(包含 3 字节头部),有效载荷只有 20 字节。
- LE Data Packet Length:链路层能承载的最大数据包长度。默认是 27 字节,但可以协商到 251 字节。
你想想看,如果 ATT MTU 只有 23 字节,那一个 PDU 只能带 20 字节数据。但如果你把 MTU 协商到 247 字节,一个 PDU 就能带 244 字节数据。效率差了多少?12 倍。
为什么会这样?因为每个 PDU 都有固定的 3 字节头部开销。小包时,头部占比高;大包时,头部占比低。我们来算笔账:
| ATT MTU | 有效载荷 | 头部开销 | 传输效率 |
|---|---|---|---|
| 23 字节 | 20 字节 | 3 字节 | 87% |
| 100 字节 | 97 字节 | 3 字节 | 97% |
| 247 字节 | 244 字节 | 3 字节 | 98.8% |
看起来效率提升不大?别急,这只是单包效率。真正的影响在链路层:
- 小 MTU 时,同样 1KB 数据要拆成 50 个包,每个包都要竞争信道、等待应答(如果是 Write Request)。
- 大 MTU 时,1KB 数据只要 4-5 个包,信道占用时间大幅减少。
我曾经在一个项目中,把 MTU 从 23 调到 247,同样的硬件,吞吐量从 8KB/s 飙到了 58KB/s。客户还以为我们换了芯片。
注意:MTU 协商是双向的。客户端发起 Exchange MTU Request,服务器回复 Exchange MTU Response。最终 MTU 取两者最小值。如果服务器只支持 100 字节,你客户端再大也没用。
3.4 吞吐量计算:理论 vs 实际
理论吞吐量很好算:
吞吐量 = (有效载荷 / 总 PDU 长度) × 连接间隔 × 每间隔包数
举个例子:
- 连接间隔 7.5ms(BLE 规范最小值)
- 每间隔传 6 包(LE 2M PHY + 大包)
- MTU = 247,有效载荷 = 244
理论吞吐量 = 244 × 6 / 0.0075 ≈ 195 KB/s
但实际呢?我测过很多次,能跑到 120-140 KB/s 就算不错了。为什么?
- 链路层重传:空口环境不好,丢包了要重传。
- 调度延迟:协议栈处理 PDU 需要时间。
- 应用层处理:你收到数据后要解析、处理,这些时间也算在内。
避坑指南:我曾经在项目里把连接间隔设到 7.5ms,结果手机端频繁断连。后来发现是手机蓝牙芯片不支持这么短的间隔。建议先查芯片手册,再定参数。
3.5 ATT PDU 与吞吐量的核心逻辑
为了让你更直观地理解,我画了一张图:
这张图把整个逻辑串起来了。从 PDU 结构出发,选择操作类型,协商 MTU,最后算出吞吐量。每一步都影响最终性能。
3.6 实战建议:如何提升吞吐量
根据上面的分析,我总结了几条实战建议:
- 优先用 Notify 和 Write Command:无应答操作省去了来回时间,吞吐量直接翻倍。
- 协商大 MTU:连接建立后立即发起 Exchange MTU Request,把 MTU 拉到 247。
- 缩短连接间隔:在芯片支持的前提下,把连接间隔设到 7.5ms-15ms。
- 用 LE 2M PHY:如果芯片支持,2M PHY 能让空口速率翻倍。
- 避免 Indicate:除非必须确保对方收到,否则别用 Indicate。
再次提醒:这些参数不是越大越好。大 MTU 意味着每个包占空口时间更长,容易受干扰。短连接间隔意味着功耗更高。要根据实际场景做权衡。