1. 为什么要聊 L2CAP 配置参数?
写过蓝牙协议栈或抓过 HCI 包的同学对 L2CAP_CONFIGURATION_REQ / RSP 一定不陌生:两个设备通过 L2CAP_CONNECT_REQ 建立面向连接的信道(Channel)之后,并不会立刻传数据,而是要进入 CONFIG 状态,互相"讨价还价"一番------你我各自能收多大的包?要不要重传?要不要校验?QoS 怎么保证?
这些"讨价还价"的内容,就是配置参数选项(Configuration Parameter Options)。协议把它们定义成一个一个的"选项"(Option),封装在信令报文里传输。
一句话理解:连接建立相当于"握手",配置过程相当于"签合同",而本文讲的选项,就是合同里的一个个条款。
L2CAP 信道建立的整体状态机是:CLOSED → WAIT_CONNECT(RSP) → CONFIG → OPEN,配置选项只在 CONFIG 阶段登场,但其中一部分参数(如 MTU、模式)在后续的 reconfigure 时也可能再次出现。
2. 配置选项的通用格式:TLV 三件套
所有配置选项都遵循统一的 Type-Length-Value 结构:

|---------------|-----------|-----------------------------------|
| 字段 | 大小 | 说明 |
| Option Type | 1 字节 | 标识要配置哪个参数 |
| Option Length | 1 字节 | Option Data 的字节数(可以没有数据,Length=0) |
| Option Data | 0~255 字节 | 具体参数值,含义由 Type 决定 |
2.1 一个非常巧妙的前向兼容设计
Option Type 的最高位(MSB)是一个"善意的开关",专门处理"老设备遇到新选项"的场景:
- Type = 0x00 ~ 0x7F(最高位为 0) :收到不认识的选项 → 必须拒绝整个配置请求;
- Type = 0x80 ~ 0xFF(最高位为 1) :收到不认识的选项 → 默默跳过,继续处理下一个。
💡 通俗解读:0x7F 以下的选项是"关键条款",不认识就不能签字(拒签整份合同);0x80 以上的选项是"附加条款",不认识就当没看见,合同照签。这样新版本协议即使加了新选项,也不会导致新旧设备之间完全无法建连。这也是为什么大家常说的"hint option(提示选项)"都把最高位置 1。
3. 7 个配置选项逐个击破
Core 5.4 中 L2CAP 经典信道(ACL-U / LE-U 上的面向连接信道)共定义了 7 个配置选项,先上一张总览表:
|--------------------------------|------|--------|--------------|------------------------------|
| 选项 | Type | Length | 可协商性 | 作用一句话 |
| MTU(最大传输单元) | 0x01 | 2 | ❌ 信息性参数 | 告诉对方"我最大能收多大的 SDU" |
| Flush Timeout | 0x02 | 2 | ✅ 可协商 | 基带层重传多久后放弃(BR/EDR) |
| QoS(服务质量) | 0x03 | 22 | ✅ 可协商 | 经典流规范:令牌桶限速 |
| Retransmission & Flow Control | 0x04 | 9 | 部分(Mode 可协商) | 选工作模式:Basic/ERTM/Streaming 等 |
| FCS(帧校验序列) | 0x05 | 1 | ❌ 不可协商 | S/I 帧要不要带 16 位 CRC |
| Extended Flow Specification | 0x06 | 16 | ❌ 不可协商 | 增强流规范,取代 QoS + Flush Timeout |
| Extended Window Size | 0x07 | 2 | ❌ 不可协商 | ERTM 模式下把窗口从 63 扩到 16383 |
3.1 MTU ------ 最大传输单元(Type 0x01)
格式:

核心要点:
- MTU 是"信息性参数",不是协商出来的值。双方各报各的,互不约束------我告诉你我最多能收多大的 SDU,你爱发多大是你的事,只要别超过我的上限。
- 最小 MTU 要求 :ACL-U 逻辑链路上必须支持 48 字节 ,LE-U 上必须支持 23 字节 ;默认值 672 字节。
- 双向 MTU 独立 :信道上行和下行的 MTU 由各自的
CONFIGURATION_REQ分别确定,天然不对称。 - 配置规则:
- 对方报的 MTU ≥ 信道最小 MTU → 必须接受;
- 对方报的 MTU < 最小值 → 可以拒绝(用 "Failure -- Unacceptable Parameters" 结果码回复,并在响应中带上自己能接受的 MTU 值);
- 响应方在正响应中应带上"实际使用的 MTU"(= 请求值与自身能力的较小者)。
5. ERTM / Streaming 模式下,MTU 不允许重配置为更小的值(只许变大方,不许变小气)。
💡 通俗类比:MTU 像是你家门口的收货门宽度------你告诉快递员"我家门最宽能过 1.5 米的家具",快递员(对端)就知道发多大的包裹你才塞得进去。至于你家往外发的家具多大,那是另一扇门的事。
3.2 Flush Timeout ------ 刷新超时(Type 0x02)

这个选项只用于 BR/EDR ,告诉对方"我在基带层打算重传到什么程度"。它是一个可协商的参数,单位为毫秒:
|------------------|-------------------------------------------------------------|
| 取值 | 含义 |
| 0x0001 | 不做基带级重传(最小轮询间隔 1.25ms) |
| 0x0002 ~ 0xFFFE | 基带实际使用的 Flush Timeout |
| 0xFFFF | 无限重传 ,直到物理链路断开 ------ 即所谓的 "可靠信道"(reliable channel) |
要点:
- 默认值 0xFFFF(无限重传);
- 该值对同一 ACL 逻辑传输上的所有信道 生效,但可以通过 HCI ACL 数据包里的 Packet_Boundary Flag(PBF) 标记单个 L2CAP 包为"不可自动刷新",实现逐包覆盖;
- 如果使用了扩展流规范(Extended Flow Spec,见 3.6),本选项禁止使用------功能被更强的替代了。
💡 通俗类比:可靠信道(0xFFFF)像"死磕型快递",丢了就一直重发,直到送到为止;小的 Flush Timeout 像"时效型快递",超过 X 毫秒没送到就直接扔掉旧包裹发新的,追求新鲜度而非完整性------语音通话就是典型场景,迟到的语音包毫无价值。
3.3 QoS ------ 经典服务质量选项(Type 0x03)
Length = 22 字节,携带一份类似 RFC 1363 的流规范(Flow Specification):

Service Type(服务类型),默认 Best Effort:
|------|-------------|------------------------------|
| 值 | 类型 | 含义 |
| 0x00 | No Traffic | 该方向上没有数据要发 |
| 0x01 | Best Effort | 尽力而为,协议强制要求支持的最低档,不给任何保证 |
| 0x02 | Guaranteed | 保证型服务,参数才有实际约束力 |
五个流量参数(Best Effort 时都可当作参考值,Guaranteed 时才有强制力):
|-------------------|----|------|---------------------------------------|------------|
| 参数 | 大小 | 单位 | 含义 | 默认值("不关心") |
| Token Rate | 4B | 字节/秒 | 应用层平均发送速率;0xFFFFFFFF = 野通配,要最大带宽 | 0x00000000 |
| Token Bucket Size | 4B | 字节 | 允许的"突发"上限(桶越大越能一波猛发);0xFFFFFFFF = 最大桶 | 0x00000000 |
| Peak Bandwidth | 4B | 字节/秒 | 限制背靠背发包的最高速度 | 0x00000000 |
| Access Latency | 4B | 微秒 | SDU 从上层到空中接口的最大可接受延迟 | 0xFFFFFFFF |
| Delay Variation | 4B | 微秒 | 最大延迟与最小延迟之差(抖动),纯信息性参数 | 0xFFFFFFFF |
令牌桶的工作原理
┌────────────────────────────────────────────┐
│ 令牌以 Token Rate 的速率持续生成 │
│ │
│ ○ ○ ○ ○ ○ ○ ──────► 桶(容量 = Token │
│ (发令牌) Bucket Size,存满即止)│
└───────────────────┬────────────────────────┘
│ 发数据时消耗令牌
▼
应用发数据 ──► 【桶里令牌够?】──是──► 正常发送(受 Peak Bandwidth 限速)
│
否
▼
非符合性流量(non-conformant)
QoS 保证不再适用(可能丢包/延迟)
其他要点:
- 只支持 Best Effort 是强制底线,其他都是可选实现;
- 请求里写 "do not care"(默认值)的参数,响应里也必须原样回 "do not care";
- Guaranteed 模式下不允许使用默认值,且响应必须把通配参数填成具体值;Token Bucket Size 还必须 ≤ MTU。
💡 通俗类比:Token Rate 是"平均月薪",Token Bucket Size 是"你的信用卡额度"(平时省着花,急用可以一波透支),Peak Bandwidth 是"单日消费上限",Access Latency 是"从下单到送达最长等多久"。
3.4 Retransmission and Flow Control Option ------ 重传与流控(Type 0x04)
Length = 9 字节,这是内容最丰富、最容易踩坑的选项:

(1)Mode ------ 信道工作模式(唯一的可协商参数)
|------|----------------------------------------|--------------------------|
| 值 | 模式 | 说明 |
| 0x00 | Basic Mode | 默认模式,最原始的透传,其余 6 个参数全部忽略 |
| 0x01 | Retransmission Mode | 重传模式(老协议遗留,仅为兼容旧设备保留) |
| 0x02 | Flow Control Mode | 流控模式(同上,仅向后兼容) |
| 0x03 | Enhanced Retransmission Mode(ERTM) | 增强重传模式,实际工程中最常用的可靠模式 |
| 0x04 | Streaming Mode | 流模式,低延迟、不重传 |
选择建议(协议原文):
- 需要可靠信道 → 用 ERTM;
- 设置了有限的 Flush Timeout → 用 Streaming;
- Retransmission / Flow Control 模式只在对方不支持 ERTM/Streaming 时出于兼容目的使用;
- 使用 ERTM/Streaming 前必须确认对端已在 Extended Features Mask (通过
L2CAP_INFORMATION_RSP交互)里声明支持,否则不能发。
(2)其余 5 个参数一览
|------------------------|----|-----------------------------------------------------------------|-----------------------------------------------------------------------------------|
| 参数 | 大小 | 作用 | 备注 |
| TxWindow Size | 1B | 发送窗口:Flow Control / Retransmission 模式取值 132,**ERTM 取值 163** | ERTM 下是信息性参数,表示最多收多少个 I-frame 不回 ACK;窗口越大信道利用率越高 |
| MaxTransmit | 1B | 同一帧最多尝试发送次数 | ERTM 下控制 I-frame 与 P=1 的 S-frame 重传次数;ERTM 下 0 = 无限重传,Retransmission 模式下禁止为 0 |
| Retransmission Timeout | 2B | 重传定时器初值(ms) | ERTM 下用于检测 I-frame 丢失并触发错误恢复 |
| Monitor Timeout | 2B | 监视定时器(ms) | ERTM 下用于检测 P=1 的 S-frame 丢失;链路空闲时周期性发 S-frame 保活 |
| MPS | 2B | 最大 PDU 载荷 | 各自声明;正响应中必须 ≤ 请求值 |
注意 MTU 与 MPS 的区别:MTU 限制的是上层 SDU(整包)的大小,MPS 限制的是每个 L2CAP PDU 分片(载荷)的大小。一个大的 SDU 会拆成多个 MPS 大小的 PDU 发送。
(3)模式冲突怎么办?------ State 1 / State 2 优先级仲裁
ERTM 和 Streaming 是双向必须一致的模式(不像老的重传/流控模式可以两方向配不同的模式)。当两边想用的模式对不上时,协议定义了两种状态:
- State 1:设备有偏好模式,但愿意妥协;
- State 2:上层(Profile)强制要求某种模式,不许妥协。
State 1 下的优先级(从高到低):
Basic Mode > Enhanced Retransmission > Streaming > 其他
⚠️ 是不是很反直觉?Basic 反而优先级最高。因为 Basic 是人人必会的模式,选它兼容性最稳。
State 1 的协商流程:
本地设备(想用 ERTM) 远端设备(偏好 Basic, State 1)
│ │
│──── CONFIGURATION_REQ (Mode=ERTM) ────►│
│ │ Basic 优先级最高,拒绝
│◄──── CONFIGURATION_RSP (Negative, │
│ 建议改用 Basic) ─────────────────│
│ │
│ 收到负响应,重新提议 Basic │
│──── CONFIGURATION_REQ (Mode=Basic) ───►│
│ │ 通过!
│◄──── CONFIGURATION_RSP (Positive) ─────│
│ │
│ (反过来远端主动发配置时同理) │
State 2 的规则非常简单粗暴 :双方模式必须完全一致,谈不拢就直接断开信道,没有第二次机会。
还有一个工程细节:信道进入 OPEN 状态之后再重配置(reconfigure) ,ERTM/Streaming 下只允许改 Retransmission Timeout 和 Monitor Timeout,其他参数一律不许改。
3.5 FCS ------ 帧校验序列(Type 0x05)

|------|-------------------------|
| 值 | 含义 |
| 0x00 | No FCS ------ S/I 帧不带校验 |
| 0x01 | 16-bit FCS(默认) |
要点:
- 不可协商;只在 ERTM 或 Streaming 模式下有效,其他模式下禁用;
- 前提同样是对端在 Extended Features Mask 里声明支持 FCS 选项;
- "No FCS" 成立的条件非常苛刻 :必须双方都在
CONFIGURATION_REQ里显式发送 FCS Type=0x00。只要有一方没发 FCS 选项、或发的不是 0x00,就用默认值 0x01(带 FCS)。
💡 通俗类比:FCS 像快递包裹上的"易碎封条"。默认贴封条(16 位校验),除非你我都白纸黑字确认"不需要封条",否则一律照贴。
3.6 Extended Flow Specification ------ 扩展流规范(Type 0x06)

|------------------------|----|----|----------------------------------------------------------|
| 参数 | 大小 | 单位 | 说明 |
| Identifier | 1B | - | 流规范的唯一标识(物理链路范围内唯一);Best Effort 固定为 0x01 |
| Service Type | 1B | - | No Traffic / Best Effort(默认)/ Guaranteed |
| Maximum SDU Size | 2B | 字节 | 应用提交的最大 SDU;0xFFFF = 未知 |
| SDU Inter-arrival Time | 4B | 微秒 | 相邻 SDU 的产生间隔;0xFFFFFFFF = 未知 |
| Access Latency | 4B | 微秒 | 空口连续发送机会的最大间隔 |
| Flush Timeout | 4B | 微秒 | SDU 各分片在 L2CAP/Controller 中存活的最长时间;0xFFFFFFFF = 永不丢弃(可靠) |
核心亮点与规则:
- 一旦双方都声明支持 Extended Flow Spec,所有信道都必须用它,且 QoS 选项和 Flush Timeout 选项同时禁用(新老不能混用);
- Guaranteed 类型细分三档:
- 保证带宽:只要速率,不管延迟(Access Latency = 0xFFFFFFFF);
- 保证延迟:只要延迟,不管速率(SDU Inter-arrival = 0xFFFFFFFF),典型如 HID 中断通道、AVRCP;
- 带宽 + 延迟双保证:典型如音视频流(A2DP);
- 一个信道不许一方向 Best Effort、另一方向 Guaranteed------这样配直接断链;但 "No Traffic + Best Effort" / "No Traffic + Guaranteed" 的组合是合法的;
- 配置完成后 Service Type 不允许通过重配置改变;
- 几个经验值:Access Latency 要扣除 HCI 传输延迟(如应用要求 20ms、HCI 延迟 5ms,就填 15ms);Flush Timeout 应大于 Access Latency,并考虑接收端去抖动缓冲的容量;Flush Timeout 填 0x00000000 表示发了就扔、绝不重传,适合游戏手柄这种"宁可要新鲜数据也不要旧数据"的场景。
3.7 Extended Window Size ------ 扩展窗口大小(Type 0x07)

ERTM 模式下 TxWindow 最大只有 63,吞吐量受限。这个选项把窗口扩到 15 位(0x0001~0x3FFF,即最多 16383 帧):
|------------------|----------|----------------|
| 取值 | ERTM | Streaming |
| 0x0000 | ❌ 无效 | ✅ 有效(启用扩展控制字段) |
| 0x0001 ~ 0x3FFF | ✅ 有效窗口大小 | ❌ 无效 |
要点:
- 不可协商,且前提是对端在 Extended Features Mask 中声明支持;
- Basic 模式下收到该选项必须忽略;
- 一旦任一方向配置成功,双方所有 S/I 帧都必须使用扩展控制字段(Extended Control Field,16 位序号)------即使只有单方向配置了该选项;
- 单方向配置成功后,反方向的最大窗口沿用 Retransmission & Flow Control 选项里的旧值。
4. 汇总
L2CAP 配置参数选项
│
┌──────────────┬───────────┼───────────┬──────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼ ▼
0x01 MTU 0x02 Flush 0x03 QoS 0x04 RTX&FC 0x05 FCS 0x06 Ext Flow Spec
收包能力 超时放弃 令牌桶流控 模式选择 帧校验 增强流规范
(信息性) (可协商) (可协商) ┌─────────┐ (不可协商) (不可协商)
│Mode: │
│Basic/ │ 0x07 Ext Window
│ERTM/ │ ERTM窗口扩到
│Streaming│ 16383 帧
└─────────┘
◄──────────── BR/EDR 专属 ◄──┘ ◄── 需 ERTM/Streaming 模式配合 ──►
再给一张速查表,开发时直接对着填参数:
|------------------|----------------------------------------------------------------|
| 开发场景 | 推荐配置 |
| 普通数据传输(如 SPP 透传) | MTU 0x01 + ERTM(0x04,Mode=0x03),大吞吐再加 0x07 扩窗口 |
| 音频流(A2DP 场景思路) | 0x06 Extended Flow Spec(Guaranteed),Flush Timeout 按缓冲容量设 |
| 游戏手柄 / 遥感类低延迟 | Streaming Mode(0x04, Mode=0x04)+ FCS=0x00 或 Flush Timeout=0 |
| 兼容老设备 | Basic Mode(一切默认即可),老的重传/流控模式仅做兼容 |
| LE 设备(ATT/GATT) | 不用本节选项!LE 固定信道直接用默认 MTU=23;LE Credit Based 信道在连接信令里直接带 MTU/MPS |
5. 写在最后:3 个最容易踩的坑
- MTU/MPS/SDU 三者的关系:MTU 管 SDU(一整包应用数据),MPS 管 PDU(线上的分片)。收端缓冲按 SDU 算,线上帧长按 MPS 算。抓包时看到一包大 SDU 被拆成多个小 PDU,就是 MPS 在起作用。
- Mode 优先级反直觉:State 1 下 Basic 模式优先级最高。别以为"我提议了高级模式对方就该听我的"------对方完全可以按协议把你打回 Basic,这不算 bug。
- 选项前提条件链 :ERTM / Streaming / FCS / Ext Window 这四个选项都依赖对端在 Extended Features Mask 中先声明支持,配置流程开始前先通过
L2CAP_INFORMATION_REQ/RSP(Feature Mask)摸清对方底细,能省掉大量无效协商。