第4章 BLE协议栈之L2CAP:数据快递的核心枢纽
L2CAP,全称逻辑链路控制与适配协议。名字挺长,但说白了,它就是BLE协议栈里的“数据快递公司”。
我在做第一个BLE项目时,对L2CAP的理解就停留在“它能把数据包切碎再拼回去”。后来踩了坑才发现,它的作用远不止这些。今天咱们就把它彻底聊透。
一句话总结:L2CAP位于HCI之上、ATT/GATT之下,负责把上层应用的数据,封装成适合底层传输的格式,并管理多个逻辑信道。
4.1 L2CAP层在协议栈中的位置
先看一张图,你就明白它站在哪了。
你看,L2CAP夹在ATT和HCI之间。上层应用的数据,先交给ATT,再交给L2CAP打包,最后通过HCI送到蓝牙芯片。反过来,收到的数据也是L2CAP先拆包,再往上送。
4.2 L2CAP的核心功能
L2CAP主要干四件事。我按重要程度排个序:
- 协议/信道复用——多个上层协议共用同一条物理链路
- 分段与重组——大包切小、小包拼大
- MTU管理——协商双方能接受的最大数据包尺寸
- 服务质量(QoS)——BLE里基本没用,但经典蓝牙里很重要
咱们重点聊前三个。QoS在BLE里几乎被砍掉了,我后面会解释。
4.3 面向连接与无连接信道
L2CAP支持两种信道类型。你想想看,这就像打电话和发广播的区别。
| 信道类型 | 特点 | BLE中使用 | 典型场景 |
|---|---|---|---|
| 面向连接信道(COC) | 一对一、可靠、有流量控制 | ✅ 常用 | ATT协议、数据透传 |
| 无连接信道(CL) | 一对多、不可靠、无确认 | ❌ 基本不用 | 广播信标(由Link Layer处理) |
面向连接信道,说白了就是建立一条专属通道。数据按顺序到达,丢了会重传。BLE里ATT协议就跑在COC上。
无连接信道呢?发出去就不管了,对方收没收到都不知道。经典蓝牙里L2CAP支持这个,但BLE里几乎用不到——因为广播数据直接在Link Layer就处理了,根本不需要L2CAP插手。
我的经验:刚开始学BLE时,我总纠结“无连接信道在哪用”。后来发现,BLE的广播和扫描都在Link Layer完成,L2CAP层根本不需要操心。所以,你只要记住COC就够了。
4.4 MTU与分段重组
MTU,最大传输单元。这是L2CAP里最容易出坑的地方。
BLE的Link Layer规定,一个数据包最大只有251字节(实际上有效载荷更少)。但上层应用可能一次要发几百甚至几千字节。怎么办?L2CAP负责切。
举个例子:
- 应用层要发1000字节
- L2CAP把它切成4个包:247 + 247 + 247 + 259
- 每个包加上L2CAP头(4字节),交给Link Layer发送
- 接收端L2CAP收到后,按顺序拼回1000字节,再交给上层
这个过程叫分段与重组(SAR)。分段在发送端,重组在接收端。
关键点:BLE的L2CAP默认MTU是23字节。你没看错,只有23字节!这是为了兼容最老的BLE设备。但实际项目中,我们都会协商更大的MTU,比如247字节,甚至512字节。
MTU协商是怎么做的?通过L2CAP的连接参数更新请求。双方交换自己支持的MTU大小,取最小值作为最终值。
// 伪代码:MTU协商过程
发起方: 我支持 MTU = 247
响应方: 我支持 MTU = 512
结果: 双方使用 MTU = min(247, 512) = 247
我曾经在一个项目中,MTU没协商好,导致数据总是发不完整。查了两天才发现,对端设备只支持默认23字节,而我这边按247字节发,数据被截断了。从那以后,我每次做BLE项目,第一件事就是确认MTU协商结果。
避坑指南:MTU协商不是自动的!你需要主动发起L2CAP连接参数更新请求。很多低端BLE芯片默认不协商,一直用23字节。如果你发现传输速度上不去,先检查MTU。
4.5 L2CAP在BLE中的简化
经典蓝牙的L2CAP很复杂,支持重传、流控、QoS、多路复用等等。但BLE为了省电和简单,砍掉了大量功能。
具体砍了什么?
| 功能 | 经典蓝牙 | BLE | 原因 |
|---|---|---|---|
| 重传与流控 | ✅ 完整支持 | ❌ 砍掉 | 由Link Layer负责 |
| QoS | ✅ 支持 | ❌ 砍掉 | BLE不保证带宽 |
| 多路复用 | ✅ 完整 | ✅ 简化版 | 只支持5个固定信道 |
| 分段重组 | ✅ 完整 | ✅ 保留 | 核心功能 |
| 无连接信道 | ✅ 支持 | ❌ 砍掉 | 由Link Layer广播替代 |
你看,BLE的L2CAP就是个“瘦身版”。它只保留了最基本的功能:信道复用和分段重组。其他的,要么交给下层(Link Layer),要么直接砍掉。
我个人觉得,这种设计很聪明。BLE的目标就是低功耗、低成本,没必要把协议栈搞得太重。你想想看,一个智能灯泡,需要QoS吗?不需要。能亮、能关、能调色就够了。
4.6 L2CAP信道与CID
每个L2CAP信道都有一个唯一的标识符,叫CID(信道ID)。就像快递单号一样,用来区分不同的数据流。
BLE里预定义了5个固定CID:
- 0x0004 — ATT协议(最常用)
- 0x0005 — L2CAP信令信道(用于MTU协商等)
- 0x0006 — 安全管理协议(SMP)
- 0x0001~0x0003 — 保留/未使用
动态CID(0x0040~0x007F)用于自定义数据通道。比如你做一个数据透传应用,就可以申请一个动态CID。
小技巧:在nRF5 SDK或Zephyr中,你可以通过bt_l2cap_chan_register()注册自定义L2CAP信道。但大多数情况下,用ATT就够了,不需要直接操作L2CAP。
4.7 实际项目中的L2CAP配置
说了这么多理论,咱们看看实际代码里L2CAP怎么配。以Zephyr RTOS为例:
// 初始化L2CAP信道
struct bt_l2cap_chan my_chan;
struct bt_l2cap_chan_ops my_ops = {
.recv = my_recv_cb,
.connected = my_connected_cb,
.disconnected = my_disconnected_cb,
};
// 注册信道
bt_l2cap_chan_register(&my_chan, &my_ops, BT_L2CAP_PSM_ATT);
// 发起MTU协商
bt_l2cap_chan_update_mtu(&my_chan, 247);
这段代码做了三件事:
- 定义信道和回调函数
- 注册到ATT协议(PSM=0x001F)
- 请求MTU升级到247字节
嗯,这里要注意:MTU协商是异步的。你调用bt_l2cap_chan_update_mtu()后,对端可能同意,也可能拒绝。结果通过回调函数通知你。
4.8 本章小结
L2CAP在BLE里虽然被简化了,但它的作用不可替代。没有它,上层应用的数据就没法被正确封装和传输。
记住几个关键点:
- L2CAP负责信道复用和分段重组
- BLE只用面向连接信道(COC)
- 默认MTU只有23字节,必须主动协商
- BLE的L2CAP是经典蓝牙的瘦身版