iOS BLE MTU 深度解析:为什么 MTU=517,但 maximumWriteValueLength 却是 512 / 514?
在开发 iOS 蓝牙通信(CoreBluetooth)时,我遇到一个比较有意思的问题:
同一个 BLE 设备,不同 iPhone 连接后,maximumWriteValueLengthForType: 返回的最大写入长度居然不同。
更奇怪的是:
-
HCI 日志显示 MTU 协商完全一样
-
但 iOS API 返回的最大写入长度却不同
本文记录完整的 实验现象 + 协议分析 + iOS 行为解释。
一、实验现象
测试设备:
-
BLE 模块
-
iPhone X
-
iPhone 12
连接建立后,通过 HCI log 观察 MTU 协商:
Exchange MTU Request: 527
Exchange MTU Response: 517
根据 Bluetooth 规范:
Effective MTU = min(527, 517)
因此:
ATT MTU = 517
iOS API 获取最大写入长度
使用 CoreBluetooth API:
NSUInteger len =
[peripheral maximumWriteValueLengthForType:type];
分别测试:
-
CBCharacteristicWriteWithResponse -
CBCharacteristicWriteWithoutResponse
得到结果:
| 设备 | 协商MTU | WriteWithResponse | WriteWithoutResponse |
|---|---|---|---|
| iPhone X | 517 | 512 | 514 |
| iPhone 12 | 517 | 512 | 512 |
于是产生两个疑问:
1️⃣ MTU 协商并没有区分 write type,为什么 API 要区分?
2️⃣ 协议最大 payload 是 514,为什么 API 返回 512?
要回答这两个问题,需要先理解 BLE 数据在协议栈中的传递过程。
二、BLE 协议栈数据路径
BLE 数据从应用层到无线空口,会经过多个协议层:
App
↓
CoreBluetooth
↓
ATT (Attribute Protocol)
↓
L2CAP
↓
Link Layer
↓
Air
MTU 只发生在 ATT 层。
三、ATT MTU 的含义
BLE 的 MTU 是 ATT(Attribute Protocol)层的最大 PDU 长度。
MTU 协商流程:
Client → Exchange MTU Request
Server → Exchange MTU Response
最终:
Effective MTU = min(Client MTU, Server MTU)
本例:
min(527, 517) = 517
因此:
ATT MTU = 517
四、ATT 写入数据结构
ATT Write PDU:
Opcode (1 byte)
Attribute Handle (2 bytes)
Attribute Value (N bytes)
因此最大 Value:
ATT payload = MTU - 3
所以:
517 - 3 = 514
也就是说:
ATT协议允许最大写入 = 514 bytes
五、ATT → L2CAP 封装(517 → 521)
ATT 数据不会直接发送到无线空口,而是会封装到 L2CAP 层。
ATT 运行在:
L2CAP fixed channel (CID = 0x0004)
L2CAP PDU 结构:
Length (2 bytes)
CID (2 bytes)
Payload (ATT PDU)
因此 L2CAP header 为:
4 bytes
所以:
ATT PDU = 517
L2CAP PDU = 517 + 4
= 521
即:
ATT 517
↓
L2CAP 521
六、Link Layer payload 的大小限制
L2CAP 数据还不会直接发送到无线空口,还需要经过 Link Layer。
BLE 的无线空口包结构:
Preamble
Access Address
LL Header
LL Payload
CRC
关键限制在:
LL Payload
BLE4.0 / BLE4.1
早期 BLE 版本:
LL payload 最大 = 27 bytes
因此如果 ATT payload 很大,就必须分片发送。
例如:
ATT payload = 514
需要:
514 / 27 ≈ 20 packets
吞吐量非常低。
BLE4.2(Data Length Extension)
BLE 4.2 引入了 Data Length Extension (DLE),大幅提高吞吐能力。
最大:
LL payload = 251 bytes
因此:
ATT 517
→ L2CAP 521
→ LL fragmentation
521 字节会被拆成:
251 + 251 + 19
因此:
MTU = ATT层限制
空口包 = Link Layer限制
两者不是同一个概念。
七、Data Length Extension 协商
在 BLE4.2 之后,连接建立后还会发生一次 Link Layer 数据长度协商:
LL_LENGTH_REQ
LL_LENGTH_RSP
协商内容:
MaxTxOctets
MaxRxOctets
MaxTxTime
MaxRxTime
如果协商成功:
MaxTxOctets = 251
如果设备不支持 DLE:
MaxTxOctets = 27
因此:
最终 LL payload 取决于双方能力
八、为什么很多时候甚至不到 251
实际开发中,经常会发现 LL payload 并没有达到 251,原因可能有多个。
1 控制器限制
有些 BLE 芯片:
MaxTxOctets = 185
这时分片可能变为:
185 + 185 + ...
2 Connection Event 时间限制
BLE 发送发生在 connection event 中。
如果一个 event 时间不够:
剩余分片 → 下一个 event
抓包时可能看到:
packet1
packet2
(下一次 event)
packet3
3 控制器 buffer 限制
控制器内部可能限制:
同时发送 packet 数量
因此:
实际吞吐 < 理论吞吐
九、为什么 iOS API 要区分 Write Type
CoreBluetooth API:
maximumWriteValueLengthForType:
参数:
-
CBCharacteristicWriteWithResponse -
CBCharacteristicWriteWithoutResponse
原因是 ATT 协议中存在两种写入方式:
| 写类型 | ATT Opcode |
|---|---|
| Write Request | 0x12 |
| Write Command | 0x52 |
区别:
| 类型 | 是否需要响应 |
|---|---|
| WithResponse | 需要 Response |
| WithoutResponse | 不需要 Response |
因此 iOS 需要分别计算最大写入长度。
十、512 是从哪里来的?
理论最大 payload:
ATT payload = 517 - 3 = 514
但 API 返回:
WriteWithResponse = 512
说明:
iOS 在 ATT payload 之上增加了一层限制
可以理解为:
AllowedWriteSize =
min(ATT_payload, iOS_internal_limit)
其中:
iOS_internal_limit ≈ 512
因此:
min(514, 512) = 512
十一、不同 iPhone 的行为差异
实验结果:
| 设备 | WithResponse | WithoutResponse |
|---|---|---|
| iPhone X | 512 | 514 |
| iPhone 12 | 512 | 512 |
说明:
-
WriteWithResponse一直被限制为 512 -
WriteWithoutResponse在某些系统版本允许 MTU-3
也就是说:
旧系统:
WithoutResponse = MTU - 3
新系统:
WithoutResponse = min(MTU - 3, 512)
这属于 CoreBluetooth 实现策略变化,不是 BLE 协议变化。
十二、为什么 HCI log 看不出这个区别
HCI log 只能看到:
ATT 协议行为
例如:
Exchange MTU
Write Command
Write Request
但:
maximumWriteValueLengthForType
属于:
CoreBluetooth Framework
层级关系:
App
↓
CoreBluetooth
↓
ATT
↓
L2CAP
↓
Link Layer
因此:
HCI log 无法看到 iOS API 的内部限制
十三、iOS BLE 开发的最佳实践
不要自己计算:
mtu - 3
正确方式:
NSUInteger maxLen =
[peripheral maximumWriteValueLengthForType:type];
然后按照该长度进行分包。
原因是:
-
不同 iOS 版本行为不同
-
不同设备行为不同
十四、总结
实验结论:
| 项目 | 数值 |
|---|---|
| 协商 MTU | 517 |
| ATT 最大 payload | 514 |
| L2CAP PDU | 521 |
| LL payload 最大 | 251 |
| iOS WithResponse | 512 |
| iOS WithoutResponse | 512 / 514 |
关键结论:
1️⃣ BLE 协议允许最大 ATT payload = MTU - 3
2️⃣ ATT 数据会封装到 L2CAP,因此 517 → 521
3️⃣ L2CAP 数据在 Link Layer 会再次分片
4️⃣ iOS CoreBluetooth 对写入长度进行了额外限制
5️⃣ maximumWriteValueLengthForType 返回的是系统允许写入的最大长度,而不是 MTU
如果你在开发 BLE SDK / OTA / 高吞吐通信,理解这些细节非常重要,否则很容易出现:
-
数据分包错误
-
写入失败
-
不同 iPhone 行为不一致
更多推荐

所有评论(0)