特征读写操作原语:读请求、写请求、写命令、通知、指示与确认
3.1 原语概述与分类
GATT 层定义了六种核心操作原语,用于客户端与服务器之间交换特征数据。根据数据传输方向与确认机制,这些原语可分为三大类:
- 客户端发起的操作:读请求、写请求、写命令
- 服务器主动推送:通知、指示
- 链路层确认:写命令无确认,指示需显式确认
| 原语 | 方向 | 需要确认 | 最大数据长度 | 典型用途 |
|---|---|---|---|---|
| 读请求 (Read Request) | Client → Server | 是(读响应) | ATT_MTU – 1 | 读取特征值 |
| 写请求 (Write Request) | Client → Server | 是(写响应) | ATT_MTU – 3 | 可靠写入 |
| 写命令 (Write Command) | Client → Server | 否 | ATT_MTU – 3 | 高速非可靠写入 |
| 通知 (Notification) | Server → Client | 否 | ATT_MTU – 3 | 实时数据推送 |
| 指示 (Indication) | Server → Client | 是(确认) | ATT_MTU – 3 | 重要事件确认 |
3.2 操作时序与数据流边界
每种原语的执行都受 ATT 层 MTU 限制。ATT_MTU 决定了单次操作可传输的最大有效载荷。常用默认 MTU 为 23 字节,扣除操作码和句柄后,特征值净荷上限见上表。实际部署中可通过 MTU 协商提升(如 512 字节)。
数据流边界规则:当特征值长度超过单次 ATT 操作净荷时,必须使用 长读取(Read Long)或 长写入(Prepare Write + Execute Write)复合流程。本课程后续章节会专项讲解。
实战提示:写命令与通知不具备链路层确认,上层应通过应用层 ACK 机制确保数据完整性;在丢包率高的 2.4GHz 环境下,优先使用写请求或指示。
3.3 协议栈 API 映射
以下以 Zephyr RTOS 的 BT GATT API 为例,展示各原语的函数调用:
// 读请求 – 客户端发起
int bt_gatt_read(struct bt_conn *conn, uint16_t handle,
uint16_t offset, struct bt_gatt_read_params *params);
// 写请求 – 可靠写,需等待响应回调
int bt_gatt_write(struct bt_conn *conn, uint16_t handle,
const void *data, uint16_t length,
bt_gatt_write_cb_t cb, void *user_data);
// 写命令 – 无确认,立即返回
int bt_gatt_write_without_response(struct bt_conn *conn,
uint16_t handle,
const void *data, uint16_t length,
bool sign);
// 服务端推送通知
int bt_gatt_notify(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *data, uint16_t len);
// 服务端推送指示
int bt_gatt_indicate(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *data, uint16_t len);
3.4 实现流程与回调模型
- 客户端读流程:发送读请求 → 服务端返回 ATT Read Response → 触发客户端读回调。
- 客户端写请求流程:发送写请求 → 服务端处理并返回写响应 → 客户端写完成回调。
- 客户端写命令流程:发送写命令 → 服务端无响应 → 客户端立即继续下一条。
- 服务端通知流程:服务端调用
bt_gatt_notify→ 链路层发送 ATT Handle Value Notification → 客户端通过订阅回调接收。 - 服务端指示流程:服务端调用
bt_gatt_indicate→ 客户端收到后必须回复 ATT Handle Value Confirmation → 服务端确认回调被调用。
常见错误:
- 混淆写命令与写请求:使用写命令时未做应用层确认导致数据丢失。
- 指示未实现确认超时处理:Zephyr 默认超时 30 秒,若客户端未回复确认,服务端需重试或断开连接。
- 单次特征值数据超过 ATT_MTU 净荷导致截断或协议错误。
3.5 调试与日志分析
使用 CONFIG_BT_DEBUG_LOG=y 开启协议栈日志,典型输出片段:
bt_gatt_write: handle 0x0012 len 4
bt_att_tx: opcode 0x52 (Write Request)
bt_att_rx: opcode 0x13 (Write Response)
bt_gatt_write_cb: err 0
通过抓取 HCI 日志可验证每个原语对应的 ATT PDU 字节流:
- 写请求 ATT PDU:
0x52 0x12 0x00 [data] - 写命令 ATT PDU:
0x52 0x12 0x00 [data](注意:写命令与写请求共用操作码 0x52,但命令端无响应) - 通知 ATT PDU:
0x1B 0x12 0x00 [data] - 指示 ATT PDU:
0x1D 0x12 0x00 [data] - 确认 ATT PDU:
0x1E
3.6 性能与稳定性优化
| 优化维度 | 策略 | 影响 |
|---|---|---|
| 减少确认开销 | 高频数据使用通知而非指示 | 降低延迟,但可能丢包 |
| MTU 协商 | 连接后将 ATT_MTU 提升至 512+ | 减少长数据拆包次数 |
| 写命令批量发送 | 利用无确认特性合并多个特征写 | 提高吞吐,但无错误反馈 |
| 指示超时重试 | 设置合理超时(如 5 秒)并实现重发逻辑 | 提高重要事件送达可靠性 |
部署建议:在低功耗蓝牙产品中,建议将“写命令 + 应用层ACK”组合用于控制类特征(如按键状态),将“指示”用于告警类特征(如电池电量临界)。实际部署前必须经过信道干扰实测。