4. MTU扩展与数据吞吐量
MTU扩展,说白了就是让蓝牙连接能一次多塞点数据。默认的23字节ATT MTU,扣掉3字节的ATT头,实际有效载荷只有20字节。你想想看,传个几百字节的数据包要拆成几十次交互,效率能高吗?
我刚开始做蓝牙项目时,就吃过这个亏。当时做OTA升级,固件有200KB,用默认MTU传,一次20字节,加上连接间隔和重传,升级一次要20多分钟。用户都投诉说“这玩意儿是不是坏了”。后来把MTU扩到247字节,时间直接砍到3分钟以内。嗯,差距就这么大。
4.1 MTU协商流程
MTU协商不是单方面决定的,它是个双向握手的过程。GATT规范里定义了MTU Exchange Request和Response这一对消息。
流程其实很简单:
- 客户端(通常是手机)发起MTU Request,告诉对方“我能接受的最大MTU是X字节”
- 服务端(你的设备)收到后,回复MTU Response,带上“我能接受的最大MTU是Y字节”
- 双方取较小值,作为本次连接实际使用的ATT MTU
关键点:协商后的MTU = min(ClientMTU, ServerMTU)。不是你单方面想扩多大就多大。
举个例子。手机说我能支持512字节,你的设备说我只支持128字节,那最终就是128字节。反过来也一样。
我在项目中遇到过一种情况:设备端明明支持大MTU,但协商结果总是只有默认的23字节。查了半天,发现是初始化代码里忘了调用MTU协商的API。嗯,这种低级错误最容易让人抓狂。
// 典型的MTU协商代码(基于Zephyr)
// 客户端发起协商
bt_gatt_exchange_mtu(conn, BT_ATT_MTU_MAX);
// 服务端处理回调
static uint8_t mtu_updated_cb(struct bt_conn *conn, uint8_t err, uint16_t mtu) {
if (err) {
printk("MTU协商失败,错误码: %d\n", err);
return BT_GATT_ERR_ATTRIBUTE_NOT_FOUND;
}
printk("协商后的MTU: %d\n", mtu);
// 根据实际MTU调整数据分片策略
return BT_GATT_ERR_SUCCESS;
}
我的习惯:协商完成后,一定要把实际MTU值存下来,后续所有数据发送逻辑都基于这个值做分片。别写死一个固定值,不同手机支持的MTU差别很大。
4.2 大MTU在弱信号下的利弊
大MTU不是万能的。尤其在弱信号场景下,它是一把双刃剑。
好处很明显:
- 单次传输的数据量大了,交互次数减少
- 协议开销占比降低,有效吞吐量提升
- 对于连续数据流(如音频、传感器数据),延迟更低
坏处呢?
- 每个包变大了,一旦丢包,重传代价更高
- 弱信号下,大包更容易被干扰,CRC错误率上升
- BLE的链路层一次最多发一个PDU,大MTU意味着占用更多空中时间,增加碰撞概率
我曾经在工厂里调试一批设备,信号强度在-80dBm到-90dBm之间。MTU设到512字节时,丢包率高达15%,重传把带宽全吃掉了。后来降到128字节,丢包率降到2%以下,实际吞吐量反而更高。
避坑指南:弱信号下不要盲目追求大MTU。我建议根据RSSI动态调整MTU大小。信号好时用大MTU提速度,信号差时用小MTU保稳定。
为什么会这样?你想想看,BLE的链路层重传机制是ARQ,每个包都要等ACK。大包传一次的时间长,如果这期间信号波动,整个包就废了。小包虽然次数多,但每次传输时间短,抗干扰能力反而强。
4.3 实际测试数据
下面是我在真实项目中测的一组数据。测试环境:nRF52840作为服务端,iPhone 13作为客户端,连接间隔固定为30ms,数据包大小固定为1000字节。
| 信号强度 (RSSI) | MTU大小 | 实际吞吐量 (KB/s) | 丢包率 | 重传次数 |
|---|---|---|---|---|
| -40 dBm | 23 | 8.2 | 0.1% | 0-1 |
| -40 dBm | 247 | 58.6 | 0.3% | 0-2 |
| -40 dBm | 512 | 72.1 | 0.5% | 1-3 |
| -70 dBm | 23 | 7.5 | 0.8% | 1-2 |
| -70 dBm | 247 | 45.2 | 2.1% | 3-5 |
| -70 dBm | 512 | 38.4 | 5.8% | 6-10 |
| -90 dBm | 23 | 4.1 | 3.2% | 4-7 |
| -90 dBm | 247 | 12.3 | 8.5% | 10-15 |
| -90 dBm | 512 | 6.7 | 18.2% | 20+ |
数据说明什么?
- 信号好时(-40dBm),MTU越大吞吐量越高,512比247提升了约24%
- 信号中等时(-70dBm),247 MTU反而比512 MTU吞吐量高,因为丢包和重传把带宽吃掉了
- 信号差时(-90dBm),247 MTU是相对最优解,512 MTU基本不可用
结论:对于大多数弱信号场景,247字节的MTU是个不错的折中点。它比默认的23字节提升了5-6倍吞吐量,又不会像512字节那样在信号波动时崩得太厉害。
我个人习惯的做法是:在连接建立后先协商到设备支持的最大MTU(通常是247或512),然后根据实际RSSI动态调整数据分片大小。信号好时一个包发满MTU,信号差时主动把每个包的有效载荷减半,宁可多传几次,也别让一个包重传个没完。
下面这张图展示了MTU协商和动态调整的整体流程:
最后说一句,MTU扩展只是优化吞吐量的第一步。你还要考虑连接间隔、数据长度扩展(DLE)、以及应用层的分包策略。这些我们后面会逐一讲到。