4. Attribute Protocol (ATT):ATT PDU格式、Handle分配机制、MTU协商流程

好,咱们进入第四章节。Attribute Protocol,简称 ATT。这玩意儿是 GATT 的底层通信协议。说白了,GATT 定义的是「数据长什么样、怎么组织」,而 ATT 定义的是「这些数据怎么在设备之间传过去」。

我刚开始接触 BLE 时,总把 ATT 和 GATT 混为一谈。后来踩了几次坑才明白——GATT 是建筑蓝图,ATT 是搬运工。没有 ATT,你定义再漂亮的 Profile 也传不过去。

4.1 ATT PDU 格式

ATT 的通信单元叫 PDU(Protocol Data Unit)。每个 PDU 的结构非常简洁,就三个部分:操作码、参数、认证签名(可选)。

字段 长度 说明
Opcode(操作码) 1 字节 标识 PDU 类型(读、写、通知等)
Parameter(参数) 可变(0~MTU-3) 具体数据内容
Authentication Signature(可选) 12 字节 用于签名验证,非必选

操作码里藏着不少信息。它的高两位决定了 PDU 的「方法类型」:

  • 0x00~0x3F:请求(Request)—— 客户端发,需要响应
  • 0x40~0x7F:响应(Response)—— 服务器回给请求的
  • 0x80~0xBF:命令(Command)—— 发出去不需要响应
  • 0xC0~0xFF:通知/指示(Notification/Indication)—— 服务器主动推数据
我的习惯:调试时先看操作码的高两位。如果服务器没响应,八成是客户端发了 Command 而不是 Request。我遇到过好几次这种低级错误,排查了半天才发现是操作码选错了。

举个例子,最常用的读请求 PDU:

Opcode: 0x0A (Read Request)
Parameters: 2 字节 Handle (小端序)

服务器收到后,回复 Read Response:

Opcode: 0x0B (Read Response)
Parameters: 可变长度 Value

嗯,这里要注意:ATT PDU 的最大长度受 MTU 限制。后面我们会详细讲 MTU 协商,先记住这个结论就行。

4.2 Handle 分配机制

每个 Attribute 都有一个唯一的 16 位 Handle,范围从 0x0001 到 0xFFFF。Handle 就是属性的「门牌号」,客户端通过 Handle 来读写属性。

Handle 的分配有讲究。我见过不少新手直接把 Handle 乱写一通,结果设备连上后读不到数据。其实规范里有一套约定俗成的规则:

  • 0x0001:通常分配给 Primary Service
  • 后续 Handle:按声明顺序递增,每个属性占一个 Handle
  • 0xFFFF:保留,不用

举个例子,一个简单的设备服务:

Handle 类型 UUID 值
0x0001 Primary Service 0x1800 Generic Access
0x0002 Characteristic 0x2A00 Device Name
0x0003 Characteristic Value 0x2A00 "MyDevice"
0x0004 Characteristic Descriptor 0x2902 0x0000
我曾经踩过的坑:有一次我写固件,Handle 从 0x0100 开始分配,结果手机端扫描服务时直接崩溃。后来查规范才发现,很多 BLE 协议栈默认 Handle 从 0x0001 开始,跳得太大会触发某些栈的边界检查。建议你从 0x0001 开始连续分配,别跳号。

Handle 还有一个重要特性:连续 Handle 可以组成一个 Group。比如一个 Service 包含多个 Characteristic,它们的 Handle 是连续的。客户端可以通过 Read By Group Type 请求,一次性读取整个 Service 的 Handle 范围。

4.3 MTU 协商流程

MTU(Maximum Transmission Unit)决定了 ATT PDU 的最大长度。默认 MTU 是 23 字节(3 字节 ATT 头 + 20 字节有效数据)。但你可以通过协商,把 MTU 增大到最高 512 字节。

为什么需要协商?你想想看,如果传输一个 100 字节的数据,默认 MTU 要拆成 5 个包,效率很低。增大 MTU 后,一个包就能搞定。

协商流程很简单,就两步:

  1. 客户端发起:发送 Exchange MTU Request,带上自己支持的 MTU 值
  2. 服务器响应:回复 Exchange MTU Response,带上自己支持的 MTU 值

最终使用的 MTU = min(客户端 MTU, 服务器 MTU)。

举个例子:

客户端 MTU = 247
服务器 MTU = 512
最终 MTU = min(247, 512) = 247

关键点:MTU 协商必须在 GATT 发现服务之前完成。如果先发现服务再协商 MTU,某些协议栈会忽略协商结果。我习惯在连接建立后的第一个 ATT 操作就做 MTU 协商。

下面这张图展示了完整的 MTU 协商流程:

MTU 协商流程图 客户端 (GATT Client) 服务器 (GATT Server) Exchange MTU Request (MTU=247) Exchange MTU Response (MTU=512) 最终 MTU = min(247, 512) = 247 注意:协商必须在 GATT 发现服务之前完成 否则某些协议栈会忽略协商结果
我的建议:如果你的设备需要传输大数据(比如 OTA 固件升级),MTU 尽量设到 247 以上。247 是 BLE 4.2 数据长度扩展后的常用值,兼容性最好。我做过测试,MTU 从 23 提升到 247,传输速度能快 5~8 倍。

最后提一句,MTU 协商不是强制的。如果你不协商,双方就用默认的 23 字节。但说实话,现在几乎没有设备还用默认 MTU 了,太慢了。我建议你每次连接都主动协商一次,花不了多少时间,但能显著提升体验。