BLE ATT(Attribute Protocol) 是蓝牙低功耗协议栈的核心,负责设备间的数据存储、发现和读写。简单说,BLE 设备之所以能"读到传感器数据"或"收到开关指令",底层都是 ATT 在起作用。
为了方便理解,我们可以把 ATT 想象成一个**"带句柄的网络文件夹系统"**,而 GATT 则是基于这个系统建立的"目录规范"。下面从零开始拆解。
1. ATT 在整个 BLE 协议栈中的位置

- ATT 位于 L2CAP 之上,固定使用信道 ID 0x0004。

- GATT 是 ATT 的上层封装,它定义了属性如何组成服务、特征、描述符。
绝大多数开发者接触的是 GATT ,但底层实际收发是 ATT PDU。
2. 核心模型:客户端/服务器
ATT 是一个显式的客户端/服务器协议:
- ATT 服务器:持有数据的一方。通常是传感器、开关等外设。
- ATT 客户端:发起读写请求的一方。通常是手机、网关。
注意: GATT 的角色(GATT 客户端/服务器)与 ATT 完全一致,只是换了个名字。
3. 什么是"属性"(Attribute)
ATT 管理的最小单元是属性(Attribute)。一个属性是一个带标签的数据单元,包含 4 个要素:
|--------------|------------|------------|------------------|
| Handle(属性句柄) | Type(属性类型) | Value(属性值) | Permission(属性权限) |
|--------|-----------|--------------------|------------------|
| 字段 | 长度 | 作用 | 举例 |
| 句柄 | 16bit | 属性的唯一 ID,类似"文件地址" | 0x0010``0x0011 |
| 类型 | 16/128bit | 属性的 UUID,类似"文件扩展名" | 0x2803(特征声明) |
| 值 | 不定长 | 实际数据 | 心率值 72bpm、开关状态 |
| 权限 | 本地存储 | 是否可读、可写、需要加密/认证 | 读需加密、写无需认证 |
关键理解:
- 句柄是访问属性的钥匙,客户端必须知道句柄才能读写。
- 类型(UUID)决定了属性的含义,比如告诉客户端"这个属性是电池电量"。
4. ATT PDU:客户端与服务器的对话
ATT 协议通过一系列固定格式的 协议数据单元 交换数据。主要分为 6 大类:
|--------|-----------|----------------------------------|----------------|
| 方向 | 操作类型 | 常见 PDU 举例 | 作用 |
| 客户端 → | 请求 | Read Request Write Request | 要求服务器回应 |
| 服务器 → | 响应 | Read Response Write Response | 回应请求 |
| 客户端 → | 命令 | Write Command | 不需要回应,速度快 |
| 服务器 → | 通知 | Handle Value Notification | 客户端不需确认 |
| 服务器 → | 指示 | Handle Value Indication | 客户端必须确认(CFM) |
| 双向 | 认证/加密 | 通过配对/绑定后链路层加密 | 权限检查在 ATT 上层完成 |
典型交互:
- 读 :
Read Req(0x10) →Read Rsp(0x0B) - 写(需要确认) :
Write Req(0x12) →Write Rsp(0x13) - 写(无确认) :
Write Cmd(0x52) - 服务器主动推送 :
Notification(0x1B) 或Indication(0x1D) →Confirmation(0x1E)
5. 属性分组与 GATT 的关系
ATT 本身不知道"服务""特征"是什么,它只知道一个个独立的属性。
GATT 做的事情 是把这些属性按照规范分组:
ATT 属性列表(按句柄连续存放):
0x0010\] UUID = 0x2800 (Primary Service) 值 = 心率服务 UUID \[0x0011\] UUID = 0x2803 (Characteristic Declaration) 值 = 属性句柄、权限、特征 UUID \[0x0012\] UUID = 0x2A37 (Heart Rate Measurement) 值 = 心率数据 \[0x0013\] UUID = 0x2902 (Client Characteristic Configuration Descriptor) 值 = 0x0001/0x0002 * **GATT 发现服务** → ATT `Read By Group Type Request` (0x10) 按 `0x2800` 分组读。 * **GATT 发现特征** → ATT `Read By Type Request` (0x08) 按 `0x2803` 读。 * **GATT 读特征值** → ATT `Read Request` (0x0A) 直接读 `0x0012` 句柄。 **所以 GATT 是 ATT 的"使用说明书"**。 ### 6. ATT 的特性与设计意图 * **低功耗、短报文** ATT PDU 最大长度默认 23 字节(可协商 MTU 扩展)。适合小数据量传感器。 * **基于句柄的高效寻址** 不像 TCP 需要长字符串标识资源,ATT 直接用 16bit 句柄,访问极快。 * **状态化缓存** 蓝牙 4.2+ GATT 缓存:客户端可以记住句柄与属性映射,重连后无需重新发现。 * **原子操作支持** `Prepare Write` + `Execute Write` 可实现事务性写入(所有修改同时生效)。 * **无连接广播(可选)** 在连接之外,通过广播包中的 **Advertising Data** 也可以包含少量属性,不需要 ATT 连接即可读取。 ### 7. 常见误区 * **"GATT 和 ATT 是并列关系"** 错。GATT **建立在** ATT 之上,ATT 是传输协议,GATT 是数据组织规范。 * **"ATT 句柄是固定的"** 不一定。每次 GATT 服务注册时由协议栈动态分配,除非开发者强制固定。 * **"每个特征只有一个属性"** 错。特征声明、特征值、描述符是**三个独立的 ATT 属性**,连续分配句柄。