4、BLE数据传输:ATT/GATT协议、MTU大小对吞吐量的影响、数据包结构
好,咱们进入第四章。这一章我打算聊聊数据传输的核心——ATT/GATT协议。说实话,很多做蓝牙开发的工程师,写了好几年代码,对这两个东西还是一知半解。我个人觉得,不理解ATT/GATT,你写出来的应用层代码大概率是跑不快的。
4.1 ATT协议:属性协议的本质
ATT,全称是Attribute Protocol。说白了,它就是一套「客户端-服务器」模型。服务器端维护一堆属性,客户端去读写这些属性。
你想想看,蓝牙设备之间怎么交换数据?总不能像TCP那样直接发字节流吧?BLE的设计哲学是「属性」——每个数据都是一个属性,有类型、有权限、有值。
ATT的核心概念:
- 属性句柄:每个属性都有一个16位的唯一ID,范围0x0001~0xFFFF
- 属性类型:用UUID标识,比如0x2A00是设备名称
- 属性权限:读、写、通知、指示等权限组合
- 属性值:实际的数据内容,长度可变
我在项目中遇到过一个问题:有个同事把属性句柄写成了0x0000,结果手机端死活连不上。嗯,这里要注意,句柄0是保留的,不能用。
4.2 GATT协议:属性的组织方式
GATT是Generic Attribute Profile的缩写。它是在ATT之上的一层封装,定义了属性怎么组织成服务(Service)和特征(Characteristic)。
我个人习惯把GATT想象成一个文件系统:
- 服务(Service):相当于文件夹,比如「电池服务」、「设备信息服务」
- 特征(Characteristic):相当于文件,比如「电池电量」、「序列号」
- 描述符(Descriptor):相当于文件的属性,比如「客户端特征配置描述符(CCCD)」
为什么会这样设计?因为BLE设备通常资源受限,不可能像HTTP那样搞复杂的请求-响应。GATT这种扁平化的结构,非常适合小数据量的交互。
避坑指南:我曾经在一个项目中,把服务和特征的数量搞到了30多个。结果连接建立后,发现发现服务(Service Discovery)过程耗时超过3秒。后来我合并了一些服务,把数量控制在10个以内,发现时间降到了500ms以内。所以,服务数量不是越多越好。
4.3 MTU大小对吞吐量的影响
MTU,Maximum Transmission Unit,最大传输单元。这个参数直接影响你的数据传输速度。
默认情况下,BLE的MTU是23字节。其中ATT头占3字节,所以有效载荷只有20字节。你想想看,传一个1KB的文件,要拆成50多个包,效率能高吗?
好在BLE 4.2之后支持MTU协商。客户端可以发起MTU请求,服务器响应后,双方使用较大的MTU。
| MTU大小 | 有效载荷(字节) | 理论吞吐量(Kbps) | 适用场景 |
|---|---|---|---|
| 23(默认) | 20 | ~10 | 传感器数据、开关状态 |
| 100 | 97 | ~50 | 音频流、批量日志 |
| 247(最大) | 244 | ~130 | 固件升级、大文件传输 |
我建议你在实际项目中,尽量把MTU协商到247。但要注意,不是所有手机都支持大MTU。我遇到过一些低端安卓手机,MTU协商到247后反而频繁断连。后来我加了个降级策略:如果247不稳定,自动回退到100。
注意:MTU协商必须在连接建立后尽快进行。如果你在数据传输过程中才去协商,可能会丢失数据包。我习惯在连接回调中立即发起MTU请求。
4.4 数据包结构详解
咱们来看看一个完整的BLE数据包长什么样。以写请求(Write Request)为例:
| 前导码(1B) | 访问地址(4B) | PDU头(2B) | 有效载荷(可变) | CRC(3B) |
PDU头里面包含:
- LLID:链路层ID,标识是数据包还是控制包
- NESN/SN:序列号和期望序列号,用于确认重传
- MD:更多数据标志,告诉对方我还有数据要发
有效载荷部分,如果是ATT写请求:
| 操作码(1B) | 句柄(2B) | 值(可变) |
操作码0x12表示写请求,0x52表示写命令(不需要响应)。
我个人习惯用写命令(Write Command)来传大量数据,因为它不需要等待响应,吞吐量更高。但要注意,写命令没有确认机制,丢了就丢了。所以关键数据还是用写请求(Write Request)或者通知(Notification)。
吞吐量计算公式:
实际吞吐量 = (有效载荷 / 总包长) × 连接间隔 × 每个连接事件能发的包数
举个例子:MTU=247,连接间隔=7.5ms,每个连接事件发6个包,那么理论吞吐量大约是:
(244 / 247) × (1000 / 7.5) × 6 × 8 ≈ 632 Kbps
嗯,这只是理论值。实际中还要考虑协议栈开销、CPU处理时间、射频干扰等。
4.5 实战建议:如何优化数据传输
讲了这么多理论,咱们来点实际的。我在几个量产项目中总结了一些经验:
- 优先使用通知(Notification)而不是指示(Indication)。通知不需要应用层确认,吞吐量高很多。指示需要确认,适合关键数据。
- 合理设置连接间隔。连接间隔越小,延迟越低,但功耗越高。我一般用7.5ms~15ms做数据传输,30ms~50ms做低功耗场景。
- 利用数据长度扩展(DLE)。BLE 4.2之后支持DLE,可以把PDU长度扩展到251字节。配合大MTU,效果更好。
- 批量发送。不要一个包一个包地发,尽量攒够一批再发。这样可以减少连接事件的浪费。
一个小技巧:我曾经在调试一个心率监测设备时,发现数据总是丢包。后来用抓包工具一看,原来是手机端的接收缓冲区太小。我调整了发送间隔,从每10ms发一个包改成每20ms发两个包,问题就解决了。有时候,问题不在发送端,而在接收端。
好了,这一章的内容就到这里。下一章我会讲BLE的功耗管理,包括如何通过调整参数来延长电池寿命。到时候咱们再聊。