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 与吞吐量的核心逻辑

为了让你更直观地理解,我画了一张图:

ATT PDU 与吞吐量核心逻辑 ATT PDU 结构 操作码(1B) | 句柄(2B) | 有效载荷(0-N B) 操作类型选择 Read/Write Request(需应答) | Write Command/Notify(无应答) Indicate(需确认) MTU 与包长协商 ATT MTU(默认23B → 最大247B) LE Data Packet Length(默认27B → 最大251B) 吞吐量 = 有效载荷 × 包数 / 时间 理论 ≈ 195KB/s | 实际 ≈ 120-140KB/s 核心:无应答操作 + 大 MTU + 短连接间隔 = 高吞吐量

这张图把整个逻辑串起来了。从 PDU 结构出发,选择操作类型,协商 MTU,最后算出吞吐量。每一步都影响最终性能。

3.6 实战建议:如何提升吞吐量

根据上面的分析,我总结了几条实战建议:

  1. 优先用 Notify 和 Write Command:无应答操作省去了来回时间,吞吐量直接翻倍。
  2. 协商大 MTU:连接建立后立即发起 Exchange MTU Request,把 MTU 拉到 247。
  3. 缩短连接间隔:在芯片支持的前提下,把连接间隔设到 7.5ms-15ms。
  4. 用 LE 2M PHY:如果芯片支持,2M PHY 能让空口速率翻倍。
  5. 避免 Indicate:除非必须确保对方收到,否则别用 Indicate。

再次提醒:这些参数不是越大越好。大 MTU 意味着每个包占空口时间更长,容易受干扰。短连接间隔意味着功耗更高。要根据实际场景做权衡。