L2CAP(Logical Link Control and Adaptation Protocol)是蓝牙协议栈中承上启下的关键协议层,负责将上层协议数据适配到下层链路层。它是蓝牙数据传输的基石,所有 Profile(如 SDP、RFCOMM、ATT、AVCTP)的数据都必须通过 L2CAP 信道传输。本文基于蓝牙核心规范与 BlueZ 5.x 全套源码,深入剖析 L2CAP 协议原理与源码实现,揭示从协议帧到 socket 操作的完整映射关系。
目录
[一、L2CAP 协议核心框架](#一、L2CAP 协议核心框架)
[二、BlueZ 用户态 L2CAP Socket 操作](#二、BlueZ 用户态 L2CAP Socket 操作)
[三、L2CAP 信道建立完整流程](#三、L2CAP 信道建立完整流程)
[五、BLE 与经典蓝牙的差异化实现](#五、BLE 与经典蓝牙的差异化实现)
[六、L2CAP 在 BlueZ 中的典型应用](#六、L2CAP 在 BlueZ 中的典型应用)
一、L2CAP 协议核心框架
1.1 协议分层定位
L2CAP 在蓝牙协议栈中的位置:

核心职责:
-
协议复用:多个上层协议共享同一物理链路
-
数据分段与重组:将上层 SDU(Service Data Unit)分段为 MTU 大小的 PDU(Protocol Data Unit)
-
信道 管理:建立、维护、释放逻辑信道
-
流量控制:防止发送方过快导致接收方缓冲区溢出
-
QoS 保障:支持不同业务的服务质量需求
1.2 帧结构详解
L2CAP 定义了两种基本帧类型:数据帧 和命令帧。
数据帧 结构
cpp
0 16 32 32+N
+--------+--------+--------+
| Length | CID | Data |
| (16bit)| (16bit)| |
+--------+--------+--------+
-
Length(16位):数据字段的长度(不包含 L2CAP 头部)
-
CID(16位):信道标识符,标识数据所属的逻辑信道
-
Data(变长):上层协议数据
BlueZ 源码 中的 数据帧 头结构体:
cpp
typedef struct {
uint16_t len;
uint16_t cid;
} __attribute__ ((packed)) l2cap_hdr;
#define L2CAP_HDR_SIZE 4
命令帧结构
cpp
0 8 16 32 32+N
+--------+--------+--------+--------+
| Code | Ident | Length | Data |
| (8bit) | (8bit) | (16bit)| |
+--------+--------+--------+--------+
-
Code(8位):命令码,标识命令类型
-
Ident(8位):标识符,用于匹配请求和响应
-
Length(16位):命令参数的长度
-
Data(变长):命令参数
BlueZ 源码 中的命令帧头结构体:
cpp
typedef struct {
uint8_t code;
uint8_t ident;
uint16_t len;
} __attribute__ ((packed)) l2cap_cmd_hdr;
#define L2CAP_CMD_HDR_SIZE 4
1.3 信道 类型
L2CAP 支持两种信道类型:
|-------------------------------|------------------|------------------|
| 信道 类型 | 特点 | 适用场景 |
| 面向连接(Connection-Oriented) | 需要建立连接、可靠传输、流量控制 | RFCOMM、ATT、AVCTP |
| 无连接(Connectionless) | 无需建立连接、不可靠传输、低延迟 | SDP 查询、广播数据 |
固定 信道 与动态信道:
cpp
/* L2CAP fixed channels */
#define L2CAP_FC_L2CAP 0x02 /* L2CAP 控制信道 */
#define L2CAP_FC_CONNLESS 0x04 /* 无连接信道 */
#define L2CAP_FC_A2MP 0x08 /* AMP 管理信道 */
固定信道(CID < 0x1000)用于协议控制,动态信道(CID >= 0x0040)由上层协议动态分配。
1.4 分片重组( SAR )机制
当上层 SDU 超过 L2CAP MTU 时,需要进行分片传输:
cpp
/* L2CAP Segmentation and Reassembly */
#define L2CAP_SAR_UNSEGMENTED 0x00 /* 未分段 */
#define L2CAP_SAR_START 0x01 /* 起始分段 */
#define L2CAP_SAR_END 0x02 /* 结束分段 */
#define L2CAP_SAR_CONTINUE 0x03 /* 中间分段 */
/* L2CAP Control Field bit masks */
#define L2CAP_CTRL_SAR_MASK 0xC000
#define L2CAP_CTRL_SAR_SHIFT 14
分片规则:
-
SDU ≤ MTU:单个数据帧,SAR = UNSEGMENTED
-
SDU > MTU:多个数据帧,SAR 依次为 START → CONTINUE → END
1.5 流量控制 模式
L2CAP 支持多种流量控制模式,在不同可靠性和延迟需求间权衡:
cpp
#define L2CAP_MODE_BASIC 0x00 /* 基本模式:无重传,无流量控制 */
#define L2CAP_MODE_RETRANS 0x01 /* 重传模式:有限重传 */
#define L2CAP_MODE_FLOWCTL 0x02 /* 流量控制模式 */
#define L2CAP_MODE_ERTM 0x03 /* 增强重传模式:可靠传输 */
#define L2CAP_MODE_STREAMING 0x04 /* 流模式:有序但可能丢包 */
|-----------|---------|----------|---------------|
| 模式 | 可靠性 | 流量控制 | 适用场景 |
| Basic | 不可靠 | 无 | 广播、低延迟场景 |
| Retrans | 有限可靠 | 无 | 对延迟敏感的可靠传输 |
| FlowCtl | 可靠 | 有 | 需要流量控制的场景 |
| ERTM | 完全可靠 | 有 | 关键数据传输(如文件传输) |
| Streaming | 有序但不可靠 | 有 | 音视频流 |
二、BlueZ 用户态 L2CAP Socket 操作
2.1 Socket 创建与绑定
BlueZ 通过标准的 socket API 创建 L2CAP 套接字:
cpp
/* 创建 L2CAP socket */
l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (l2cap_sock < 0) {
error("opening L2CAP socket: %s", strerror(errno));
return -1;
}
/* 设置地址结构 */
struct sockaddr_l2 l2addr;
memset(&l2addr, 0, sizeof(l2addr));
l2addr.l2_family = AF_BLUETOOTH;
bacpy(&l2addr.l2_bdaddr, BDADDR_ANY);
l2addr.l2_psm = htobs(SDP_PSM);
/* 绑定到指定 PSM */
if (bind(l2cap_sock, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
error("binding L2CAP socket: %s", strerror(errno));
return -1;
}
sockaddr_l2 结构体详解:
cpp
struct sockaddr_l2 {
sa_family_t l2_family; /* 地址族:AF_BLUETOOTH */
unsigned short l2_psm; /* 协议/服务复用器(用于绑定/连接) */
bdaddr_t l2_bdaddr; /* 蓝牙地址 */
unsigned short l2_cid; /* 信道标识符(用于固定信道) */
uint8_t l2_bdaddr_type; /* 地址类型:BDADDR_BREDR/BDADDR_LE_PUBLIC/BDADDR_LE_RANDOM */
};
PSM(Protocol/Service Multiplexer):用于标识上层协议,类似 TCP/UDP 的端口号。
2.2 btio 封装层
BlueZ 提供了 btio 库封装 L2CAP 操作,使上层代码更简洁:
绑定函数:
cpp
static int l2cap_bind(int sock, const bdaddr_t *src, uint8_t src_type,
uint16_t psm, uint16_t cid, GError **err)
{
struct sockaddr_l2 addr;
memset(&addr, 0, sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, src);
if (cid)
addr.l2_cid = htobs(cid); /* 使用 CID(固定信道) */
else
addr.l2_psm = htobs(psm); /* 使用 PSM(动态信道) */
addr.l2_bdaddr_type = src_type;
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
int error = -errno;
ERROR_FAILED(err, "l2cap_bind", errno);
return error;
}
return 0;
}
连接函数:
cpp
static int l2cap_connect(int sock, const bdaddr_t *dst, uint8_t dst_type,
uint16_t psm, uint16_t cid)
{
int err;
struct sockaddr_l2 addr;
memset(&addr, 0, sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, dst);
if (cid)
addr.l2_cid = htobs(cid);
else
addr.l2_psm = htobs(psm);
addr.l2_bdaddr_type = dst_type;
err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
return -errno;
return 0;
}
注意 connect() 返回 EAGAIN 或 EINPROGRESS 是正常的,因为 L2CAP 连接建立需要时间(涉及多轮命令交互)。
2.3 选项设置
MTU 设置
cpp
static gboolean set_l2opts(int sock, uint16_t imtu, uint16_t omtu,
uint8_t mode, GError **err)
{
struct l2cap_options l2o;
socklen_t len;
memset(&l2o, 0, sizeof(l2o));
len = sizeof(l2o);
if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) {
ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno);
return FALSE;
}
if (imtu)
l2o.imtu = imtu;
if (omtu)
l2o.omtu = omtu;
if (mode) {
l2o.mode = mode_l2mode(mode);
if (l2o.mode == UINT8_MAX) {
ERROR_FAILED(err, "Unsupported mode", errno);
return FALSE;
}
}
if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) {
ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno);
return FALSE;
}
return TRUE;
}
l2cap_options 结构体:
cpp
struct l2cap_options {
uint16_t omtu; /* 输出 MTU */
uint16_t imtu; /* 输入 MTU */
uint16_t flush_to; /* 刷新超时 */
uint8_t mode; /* 传输模式 */
uint8_t fcs; /* 帧检查序列 */
uint8_t max_tx; /* 最大重传次数 */
uint16_t txwin_size; /* 发送窗口大小 */
};
安全级别设置
cpp
static int l2cap_set_lm(int sock, int level)
{
int lm_map[] = {
0,
L2CAP_LM_AUTH, /* 认证 */
L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT, /* 认证 + 加密 */
L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE, /* 认证 + 加密 + 安全连接 */
}, opt = lm_map[level];
if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0)
return -errno;
return 0;
}
三、L2CAP 信道建立完整流程
3.1 建立流程时序图

3.2 连接请求/响应
连接请求结构体:
cpp
typedef struct {
uint16_t psm; /* 目标 PSM */
uint16_t scid; /* 源信道 ID */
} __attribute__ ((packed)) l2cap_conn_req;
#define L2CAP_CONN_REQ_SIZE 4
typedef struct {
uint16_t dcid; /* 目标信道 ID */
uint16_t scid; /* 源信道 ID */
uint16_t result; /* 连接结果 */
uint16_t status; /* 状态 */
} __attribute__ ((packed)) l2cap_conn_rsp;
#define L2CAP_CONN_RSP_SIZE 8
连接结果码:
cpp
#define L2CAP_CR_SUCCESS 0x0000 /* 成功 */
#define L2CAP_CR_PEND 0x0001 /* 挂起 */
#define L2CAP_CR_BAD_PSM 0x0002 /* PSM 无效 */
#define L2CAP_CR_SEC_BLOCK 0x0003 /* 安全阻塞 */
#define L2CAP_CR_NO_MEM 0x0004 /* 内存不足 */
3.3 配置请求/响应
配置请求结构体:
cpp
typedef struct {
uint16_t dcid; /* 目标信道 ID */
uint16_t flags; /* 标志位 */
uint8_t data[0]; /* 配置选项 */
} __attribute__ ((packed)) l2cap_conf_req;
#define L2CAP_CONF_REQ_SIZE 4
typedef struct {
uint16_t scid; /* 源信道 ID */
uint16_t flags; /* 标志位 */
uint16_t result; /* 配置结果 */
uint8_t data[0]; /* 配置选项 */
} __attribute__ ((packed)) l2cap_conf_rsp;
#define L2CAP_CONF_RSP_SIZE 6
配置选项类型:
cpp
#define L2CAP_CONF_MTU 0x01 /* MTU */
#define L2CAP_CONF_FLUSH_TO 0x02 /* 刷新超时 */
#define L2CAP_CONF_QOS 0x03 /* QoS 参数 */
#define L2CAP_CONF_RFC 0x04 /* 重传/流量控制 */
#define L2CAP_CONF_FCS 0x05 /* 帧检查序列 */
#define L2CAP_CONF_EFS 0x06 /* 扩展特性 */
#define L2CAP_CONF_EWS 0x07 /* 扩展窗口大小 */
3.4 断连流程
cpp
typedef struct {
uint16_t dcid;
uint16_t scid;
} __attribute__ ((packed)) l2cap_disconn_req;
typedef struct {
uint16_t dcid;
uint16_t scid;
} __attribute__ ((packed)) l2cap_disconn_rsp;
四、数据收发与流量控制
4.1 数据发送流程
在 BlueZ 中,上层协议通过 write() 或 send() 发送数据:
cpp
/* att.c 中的数据发送 */
static ssize_t att_send_pdu(struct bt_att *att, uint8_t opcode,
const void *pdu, uint16_t len)
{
struct att_send_op *op;
uint8_t *buf;
/* 检查 MTU */
if (len > att->mtu) {
att_log(att, ATT_LOG_ERROR, "PDU too big (%u > %u)", len, att->mtu);
return -EINVAL;
}
buf = malloc(len + 1);
if (!buf)
return -ENOMEM;
buf[0] = opcode;
memcpy(buf + 1, pdu, len);
/* 入队等待发送 */
op = g_new0(struct att_send_op, 1);
op->opcode = opcode;
op->pdu = buf;
op->len = len + 1;
queue_push_head(att->write_queue, op);
/* 触发写入 */
if (!att->writer_active)
att_writer(att);
return op->len;
}
4.2 MTU 获取
cpp
static uint16_t io_get_mtu(int fd)
{
socklen_t len;
struct l2cap_options l2o;
len = sizeof(l2o);
if (!getsockopt(fd, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len))
return l2o.omtu;
if (!getsockopt(fd, SOL_BLUETOOTH, BT_SNDMTU, &l2o.omtu, &len))
return l2o.omtu;
return 0;
}
4.3 ERTM 模式流量控制
ERTM(Enhanced Retransmission Mode)是最常用的可靠传输模式:

监控器中的 ERTM 帧解析:
cpp
/* L2CAP Control Field bit masks */
#define L2CAP_CTRL_REQSEQ_MASK 0x3F00
#define L2CAP_CTRL_TXSEQ_MASK 0x007E
#define L2CAP_CTRL_SUPERVISE_MASK 0x000C
#define L2CAP_CTRL_POLL 0x0010
#define L2CAP_CTRL_FINAL 0x0080
#define L2CAP_CTRL_FRAME_TYPE 0x0001 /* I- or S-Frame */
/* L2CAP Supervisory Function */
#define L2CAP_SUPER_RR 0x00 /* 接收准备好 */
#define L2CAP_SUPER_REJ 0x01 /* 拒绝 */
#define L2CAP_SUPER_RNR 0x02 /* 接收未准备好 */
#define L2CAP_SUPER_SREJ 0x03 /* 选择性拒绝 */
五、BLE 与经典蓝牙的差异化实现
5.1 PSM 差异
|--------|--------------------|---------------------------------|
| 特性 | 经典蓝牙 | BLE |
| PSM 分配 | 静态分配(如 SDP=0x0001) | 动态分配(0x0080-0xFFFF) |
| CoC 支持 | 不支持 | 支持 Connection-Oriented Channels |
cpp
#define BREDR_DEFAULT_PSM 0x1011
#define LE_DEFAULT_PSM 0x0080 /* BLE 动态 PSM 起始 */
5.2 MTU 差异
|------------|------------|------------|
| 协议 | 默认 MTU | 最大 MTU |
| 经典蓝牙 L2CAP | 672 | 65535 |
| BLE L2CAP | 23(ATT 默认) | 517 |
5.3 BLE 特有设置
cpp
static gboolean set_le_imtu(int sock, uint16_t imtu, GError **err)
{
/* BLE 使用 BT_RCVMTU 而非 L2CAP_OPTIONS */
if (setsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, &imtu,
sizeof(imtu)) < 0) {
ERROR_FAILED(err, "setsockopt(BT_RCVMTU)", errno);
return FALSE;
}
return TRUE;
}
static gboolean set_le_mode(int sock, uint8_t mode, GError **err)
{
/* BLE 使用 BT_MODE 而非 L2CAP_OPTIONS */
if (setsockopt(sock, SOL_BLUETOOTH, BT_MODE, &mode,
sizeof(mode)) < 0) {
ERROR_FAILED(err, "setsockopt(BT_MODE)", errno);
return FALSE;
}
return TRUE;
}
5.4 BLE 基于信用的流量控制
BLE 使用基于信用(Credit-based)的流量控制机制,与经典蓝牙的滑动窗口机制不同:
cpp
#define L2CAP_MODE_LE_FLOWCTL 0x80 /* BLE 流量控制模式 */
#define L2CAP_MODE_ECRED 0x81 /* 增强信用模式 */
六、L2CAP 在 BlueZ 中的典型应用
6.1 SDP 服务发现
cpp
/* SDP 服务器初始化 */
static int init_server(uint16_t mtu, int central, int compat)
{
/* 创建 L2CAP socket */
l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
...
/* 绑定到 SDP_PSM (0x0001) */
l2addr.l2_psm = htobs(SDP_PSM);
bind(l2cap_sock, (struct sockaddr *) &l2addr, sizeof(l2addr));
/* 设置 MTU */
opts.omtu = mtu;
opts.imtu = mtu;
setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts));
/* 监听 */
listen(l2cap_sock, 5);
...
}
6.2 GATT Client 通信
cpp
static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
{
...
/* BLE 设备需要交换 MTU */
if (bt_att_get_link_type(client->att) == BT_ATT_BREDR)
goto discover;
mtu = MAX(BT_ATT_DEFAULT_LE_MTU, mtu);
if (mtu == BT_ATT_DEFAULT_LE_MTU)
goto discover;
/* 发送 MTU 交换请求 */
client->mtu_req_id = bt_gatt_exchange_mtu(client->att, mtu,
exchange_mtu_cb, ...);
...
}
6.3 Profile L2CAP 服务器
cpp
static void ext_start_servers(struct ext_profile *ext, struct btd_adapter *adapter)
{
...
if (ext->local_psm) {
uint16_t psm;
if (ext->local_psm > 0)
psm = ext->local_psm;
else
psm = 0;
/* 创建 L2CAP 监听服务器 */
io = bt_io_listen(connect, confirm, l2cap, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR,
btd_adapter_get_address(adapter),
BT_IO_OPT_MODE, ext->mode,
BT_IO_OPT_PSM, psm,
BT_IO_OPT_SEC_LEVEL, ext->sec_level,
BT_IO_OPT_INVALID);
...
}
...
}
七、开发要点与常见问题
7.1 开发要点
1. Socket 类型选择
cpp
/* 面向连接的可靠传输 */
sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
/* 无连接的不可靠传输 */
sock = socket(PF_BLUETOOTH, SOCK_DGRAM, BTPROTO_L2CAP);
2. PSM 分配规范
-
动态 PSM(0x0080-0xFFFF):运行时动态分配
-
静态 PSM(0x0001-0x007F):需要蓝牙 SIG 注册
3. MTU 协商
cpp
/* 获取当前 MTU */
struct l2cap_options opts;
getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts));
printf("Current MTU: %d\n", opts.omtu);
/* 设置 MTU(需要在 connect/bind 之前) */
opts.omtu = 1024;
opts.imtu = 1024;
setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts));
4. 异步连接处理
cpp
/* 非阻塞连接 */
fcntl(sock, F_SETFL, O_NONBLOCK);
err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
if (err < 0) {
if (errno == EINPROGRESS) {
/* 连接进行中,等待完成 */
struct pollfd fds;
fds.fd = sock;
fds.events = POLLOUT;
poll(&fds, 1, timeout);
} else {
/* 真正的错误 */
perror("connect");
close(sock);
return -1;
}
}
7.2 常见问题与解决方案
问题 1:connect 返回 EINPROGRESS
原因:L2CAP 连接需要多轮命令交互,无法立即完成。
解决方案:使用非阻塞模式 + poll/select 等待连接完成。
问题 2:数据发送失败,errno = EMSGSIZE
原因:发送数据超过 MTU。
解决方案:
cpp
/* 检查数据大小 */
if (data_len > mtu) {
/* 需要应用层分片 */
uint16_t offset = 0;
while (offset < data_len) {
uint16_t chunk_len = MIN(data_len - offset, mtu);
send(sock, data + offset, chunk_len, 0);
offset += chunk_len;
}
}
问题 3:BLE 设备连接失败
原因:地址类型错误。
解决方案:
cpp
struct sockaddr_l2 addr;
addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; /* 或 BDADDR_LE_RANDOM */
问题 4:ERTM 模式下数据丢失
原因:发送窗口耗尽,需要等待接收方的 RR 帧。
解决方案:检查 txwin_size 设置,确保发送速率匹配接收能力。
八、总结
L2CAP 作为蓝牙协议栈的数据传输基石,其设计体现了协议复用、流量控制、可靠传输等核心思想。BlueZ 通过标准 socket API 和 btio 封装层,将复杂的 L2CAP 协议转化为简洁的编程接口:
帧结构:数据帧(l2cap_hdr)+ 命令帧(l2cap_cmd_hdr)
信道管理:通过 PSM/CID 标识,支持固定信道和动态信道
流量控制:Basic/Retrans/ERTM/Streaming 四种模式
差异化实现:BLE 使用动态 PSM、基于信用的流量控制
开发建议:
-
使用 btio 库简化 L2CAP 操作
-
根据业务需求选择合适的传输模式
-
注意 BLE 与经典蓝牙的差异
-
合理设置 MTU 和安全级别
附录:核心函数索引
|-------------------|---------------|------------------|
| 文件 | 函数 | 功能 |
| btio/btio.c | l2cap_bind | L2CAP socket 绑定 |
| btio/btio.c | l2cap_connect | L2CAP socket 连接 |
| btio/btio.c | set_l2opts | 设置 L2CAP 选项 |
| btio/btio.c | l2cap_set_lm | 设置安全级别 |
| btio/btio.c | set_le_imtu | 设置 BLE 输入 MTU |
| src/sdpd-server.c | init_server | SDP L2CAP 服务器初始化 |
| src/shared/att.c | io_get_mtu | 获取 L2CAP MTU |
| lib/l2cap.h | l2cap_hdr | 数据帧头结构 |
| lib/l2cap.h | l2cap_cmd_hdr | 命令帧头结构 |