3. GATT 操作与流程:Read、Write、Notify、Indicate 详解
GATT 操作,说白了就是蓝牙设备之间怎么交换数据。我刚开始做蓝牙开发时,总觉得 GATT 就是几个读写函数调一调。后来被坑了几次才明白——操作类型选不对,数据就丢了;MTU 没协商好,吞吐量上不去;Connection Interval 设错了,功耗和速度全乱套。
这一章,咱们就把这些核心操作掰开揉碎讲清楚。
3.1 四大操作:Read、Write、Notify、Indicate
GATT 定义了四种数据交互方式。每种都有自己的适用场景,选错了会出大问题。
| 操作类型 | 方向 | 是否需要应答 | 典型场景 |
|---|---|---|---|
| Read | Client → Server | 是(Server 回复数据) | 读取设备信息、状态查询 |
| Write | Client → Server | 可选(Write Request/Command) | 下发配置、控制指令 |
| Notify | Server → Client | 否(无应答) | 实时数据推送(传感器、心率) |
| Indicate | Server → Client | 是(Client 回复确认) | 关键事件上报(报警、状态变更) |
3.1.1 Read 操作
Read 是最基础的操作。Client 发一个 Read Request,Server 把 Attribute Value 返回。
这里有个坑——一次 Read 最多能读多少字节? 答案是:取决于 MTU 大小。默认 MTU 是 23 字节,减去 3 字节的 ATT 头部,实际有效载荷只有 20 字节。如果你要读的数据超过 20 字节,就得用 Read Blob Request 分片读取。
3.1.2 Write 操作
Write 分两种:Write Request 和 Write Command。
- Write Request:Client 发数据,Server 必须回复 Write Response。可靠,但慢。
- Write Command:Client 发数据,Server 不回复。快,但不可靠。
我遇到过一个问题:用 Write Command 下发固件升级数据,结果丢包了,设备直接变砖。后来我改成 Write Request,虽然慢了点,但至少能保证每一包都到了。
3.1.3 Notify 与 Indicate
这两个是 Server 主动给 Client 推数据的操作。区别在于:
- Notify:Server 发完就不管了,Client 不需要回复。速度快,但可能丢包。
- Indicate:Server 发完,Client 必须回复确认。可靠,但慢。
你想想看,心率传感器每秒出 10 个数据,用 Indicate 的话,每个数据都要等确认,吞吐量直接腰斩。所以心率数据一般用 Notify。但如果是报警信号——比如温度超限——那就必须用 Indicate,确保 App 收到了。
- 数据量大、允许偶尔丢包 → Notify
- 数据量小、必须可靠 → Indicate
3.2 MTU 交换流程
MTU(Maximum Transmission Unit)决定了单次 ATT 包能传多少数据。默认 MTU 是 23 字节,但大部分现代蓝牙芯片都支持更大的 MTU,比如 247 字节甚至 512 字节。
MTU 交换的流程很简单:
- Client 发送 MTU Request,告诉 Server:“我能支持的最大 MTU 是 X”。
- Server 回复 MTU Response,告诉 Client:“我能支持的最大 MTU 是 Y”。
- 双方取 min(X, Y) 作为本次连接的 MTU。
举个例子:Client 说我能到 247,Server 说我能到 128,那最终 MTU 就是 128。有效载荷就是 128 - 3 = 125 字节。
MTU 交换的时机也很重要。有些芯片在连接后会自动协商,有些需要手动触发。我的习惯是:连接建立后 100ms 内发起 MTU Request,避免后续操作因为 MTU 太小而失败。
3.3 Connection Interval 对吞吐量的影响
Connection Interval 是蓝牙连接中两个连接事件之间的时间间隔。单位是 1.25ms,范围从 7.5ms 到 4s。
Connection Interval 越小,吞吐量越高,但功耗也越大。
咱们算一笔账:
- 假设 MTU 是 247 字节,有效载荷 244 字节(减去 L2CAP 头部)。
- 每个连接事件可以传 4 个包(取决于芯片和协议栈)。
- Connection Interval 是 7.5ms,那一秒就有 133 个连接事件。
理论吞吐量 = 244 字节 × 4 包 × 133 事件/秒 ≈ 130 KB/s。
但如果 Connection Interval 是 50ms,那一秒只有 20 个事件,吞吐量就掉到 244 × 4 × 20 ≈ 19.5 KB/s。
| Connection Interval | 事件数/秒 | 理论吞吐量(MTU=247, 4包/事件) | 典型场景 |
|---|---|---|---|
| 7.5ms | 133 | ~130 KB/s | 音频流、大数据传输 |
| 15ms | 66 | ~64 KB/s | 传感器数据流 |
| 30ms | 33 | ~32 KB/s | 周期性状态上报 |
| 50ms | 20 | ~19 KB/s | 低功耗设备 |
Connection Interval 不是你想设多少就设多少。它需要 Client 和 Server 协商。Client 发起 Connection Update Request,Server 决定是否接受。如果 Server 不接受,连接就会断开。
我的经验是:不要一上来就设 7.5ms。先设一个中间值,比如 15ms,看看连接稳定性。如果稳定,再逐步减小。有些老手机不支持 7.5ms,强行设会导致断连。
3.4 知识体系总览
下面这张图把 GATT 操作、MTU 交换、Connection Interval 的关系串起来了。你看完应该能对整个流程有个全局认识。
3.5 实战建议
讲了这么多理论,最后给几条实战建议:
- 连接后先做 MTU 交换,别等传数据时才发现不够用。
- 根据数据特性选操作类型:大数据流用 Notify,关键事件用 Indicate。
- Connection Interval 从中间值开始调,稳定后再往小改。别一上来就 7.5ms。
- 实测吞吐量:理论值只是参考,实际跑一下才知道真实性能。
一句话总结: GATT 操作选型看可靠性需求,MTU 决定单包大小,Connection Interval 决定发包频率。三者配合好了,吞吐量才能上去。