当前位置: 首页 > news >正文

【Bluedroid】蓝牙启动之sdp_init 源码解析

本文围绕Android蓝牙协议栈中 SDP(Service Discovery Protocol,服务发现协议)模块的初始化函数sdp_init展开,结合核心控制块tSDP_CB及关联数据结构(如tL2CAP_CFG_INFOtCONN_CB等)的定义与协作逻辑,详细解析 SDP 模块初始化的关键步骤,包括控制块内存初始化、连接定时器创建、L2CAP 通信参数配置、回调函数注册及服务注册流程。同时,阐明各数据结构如何支撑 SDP 模块的状态管理、连接控制与协议交互,为理解蓝牙服务发现的底层机制提供技术视角。

一、概述

1.1 SDP 模块的核心地位

SDP是蓝牙协议栈的核心组件,负责设备间服务的发现与信息交换(如查询耳机支持的音频服务、获取打印机的文件传输协议)。其核心功能依赖初始化阶段的资源准备与协议绑定,sdp_init函数是 SDP 模块启动的入口,承担模块运行环境的初始化任务。

1.2 sdp_init的关键初始化步骤

  1. 控制块内存初始化:通过memset清零全局控制块tSDP_CB sdp_cb,确保所有运行时状态(如连接信息、配置参数)从初始状态开始。

  2. 连接定时器创建:为每个并发连接(由SDP_MAX_CONNECTIONS定义)分配定时器sdp_conn_timer,用于监控连接超时,保障连接生命周期管理。

  3. L2CAP 通信参数配置:设置sdp_cb.l2cap_my_cfg的 MTU(最大传输单元)为SDP_MTU_SIZE,明确与底层 L2CAP 协议协商的通信能力(如单次传输的最大数据量)。

  4. L2CAP 事件回调注册:sdp_cb.reg_info绑定sdp_connect_indsdp_data_ind等回调函数,定义 L2CAP 连接请求、数据到达、断开等事件的处理逻辑,构建 SDP 与 L2CAP 的交互桥梁。

  5. 向 L2CAP 注册 SDP 服务:调用L2CA_Register2向 L2CAP 注册 SDP 服务(PSM 为BT_PSM_SDP),完成协议栈底层的服务绑定,确保 SDP 能接收 L2CAP 传递的事件与数据。

1.3 核心数据结构的协作机制

SDP 模块的高效运行依赖多个数据结构的协同:

  • tSDP_CB:全局控制块,整合 L2CAP 配置(l2cap_my_cfg)、连接状态(ccb数组)、本地服务数据库(server_db)等核心信息,是模块运行的 “中枢”。

  • tL2CAP_CFG_INFO:L2CAP 配置参数结构体,通过可选参数标记(如mtu_presentqos_present)灵活协商 MTU、QoS 等通信能力,适配不同业务场景(如实时音频的低延迟需求与文件传输的可靠性需求)。

  • tCONN_CB:连接控制块,管理单个 SDP 连接的状态(con_state)、定时器(sdp_conn_timer)、数据缓存(rsp_list)及发现结果(handles数组),支撑多连接并发处理。

  • tL2CAP_APPL_INFO:L2CAP 回调注册结构体,绑定pL2CA_DataInd_Cb等事件处理函数,实现 SDP 对 L2CAP 事件的响应(如数据到达时解析 SDP 请求)。

二、源码分析

sdp_init

packages/modules/Bluetooth/system/stack/sdp/sdp_main.cc
/******************************************************************************/
/*                     G L O B A L      S D P       D A T A                   */
/******************************************************************************/
// SDP 模块的核心控制块(Control Block)结构体类型
// 用于存储 SDP 运行时的状态、连接信息、配置参数等关键数据
tSDP_CB sdp_cb;/* The maximum number of simultaneous client and server connections. */
#ifndef SDP_MAX_CONNECTIONS
#define SDP_MAX_CONNECTIONS 4 // 表示 SDP 支持的最大并发连接数
#endif/********************************************************************************* Function         sdp_init** Description      This function initializes the SDP unit.** Returns          void*******************************************************************************/
void sdp_init(void) {/* Clears all structures and local SDP database (if Server is enabled) */memset(&sdp_cb, 0, sizeof(tSDP_CB));// 初始化连接定时器for (int i = 0; i < SDP_MAX_CONNECTIONS; i++) {sdp_cb.ccb[i].sdp_conn_timer = alarm_new("sdp.sdp_conn_timer");}// 配置 L2CAP MTU/* Initialize the L2CAP configuration. We only care about MTU */sdp_cb.l2cap_my_cfg.mtu_present = true; // 启用 MTU(最大传输单元)配置sdp_cb.l2cap_my_cfg.mtu = SDP_MTU_SIZE;// 设置 SDP 搜索参数sdp_cb.max_attr_list_size = SDP_MTU_SIZE - 16;sdp_cb.max_recs_per_search = SDP_MAX_DISC_SERVER_RECS;// 注册 L2CAP 回调函数sdp_cb.reg_info.pL2CA_ConnectInd_Cb = sdp_connect_ind; // 处理 L2CAP 连接请求指示(当其他设备请求与本地 SDP 建立连接时触发)sdp_cb.reg_info.pL2CA_ConnectCfm_Cb = sdp_connect_cfm;sdp_cb.reg_info.pL2CA_ConfigInd_Cb = sdp_config_ind;sdp_cb.reg_info.pL2CA_ConfigCfm_Cb = sdp_config_cfm;sdp_cb.reg_info.pL2CA_DisconnectInd_Cb = sdp_disconnect_ind; // 处理 L2CAP 断开连接指示(当连接异常断开时触发)sdp_cb.reg_info.pL2CA_DisconnectCfm_Cb = sdp_disconnect_cfm;sdp_cb.reg_info.pL2CA_DataInd_Cb = sdp_data_ind; // 处理 L2CAP 数据到达指示(当收到对端通过 L2CAP 发送的 SDP 数据时触发)sdp_cb.reg_info.pL2CA_Error_Cb = sdp_on_l2cap_error;// 向 L2CAP 注册 SDP 服务/* Now, register with L2CAP */if (!L2CA_Register2(BT_PSM_SDP, sdp_cb.reg_info, true /* enable_snoop */,nullptr, SDP_MTU_SIZE, 0, BTM_SEC_NONE)) {log::error("SDP Registration failed");}
}

蓝牙协议栈中 SDP(服务发现协议,Service Discovery Protocol)模块的初始化函数,主要完成:

  • 控制块内存初始化;

  • 连接定时器创建;

  • L2CAP 通信参数配置;

  • L2CAP 事件回调注册;

  • 向 L2CAP 注册 SDP 服务,建立与底层协议的交互通道。

tSDP_CB sdp_cb

/*  The main SDP control block */
typedef struct {// 存储 SDP 模块向 L2CAP声明的本地配置参数,用于协商与对端设备的通信能力tL2CAP_CFG_INFO l2cap_my_cfg; /* My L2CAP config     */// 管理 SDP 的并发连接状态,每个元素对应一个与对端设备的 SDP 连接tCONN_CB ccb[SDP_MAX_CONNECTIONS];// 存储本地设备的服务记录数据库tSDP_DB server_db;// 存储 SDP 模块向 L2CAP 注册的事件回调函数,是 SDP 与 L2CAP 交互的桥梁tL2CAP_APPL_INFO reg_info;    /* L2CAP Registration info */// 限制 SDP 搜索或查询操作中,单个属性列表的最大长度(单位:字节)uint16_t max_attr_list_size;  /* Max attribute list size to use   */// 限制 SDP 搜索操作中,每次返回的最大服务记录数(单位:条)uint16_t max_recs_per_search; /* Max records we want per seaarch  */
} tSDP_CB;

tSDP_CB用于存储 SDP 运行时的关键状态、配置参数和交互信息。它是 SDP 模块与底层 L2CAP 协议、上层应用以及内部连接管理的枢纽。

整合了 SDP 模块运行所需的核心信息:

  • 与 L2CAP 的交互配置(l2cap_my_cfgreg_info);

  • 连接状态管理(ccb 数组);

  • 本地服务数据存储(server_db);

  • 搜索参数限制(max_attr_list_sizemax_recs_per_search)。

通过这个控制块,SDP 模块能够高效处理服务发现请求、管理并发连接,并与蓝牙协议栈的底层(L2CAP)和上层(应用)协同工作。

tL2CAP_CFG_INFO

packages/modules/Bluetooth/system/stack/include/l2c_api.h
/* Define a structure to hold the configuration parameters. Since the* parameters are optional, for each parameter there is a boolean to* use to signify its presence or absence.*/
typedef struct {uint16_t result; /* Only used in confirm messages */bool mtu_present;uint16_t mtu;bool qos_present;FLOW_SPEC qos;bool flush_to_present; // 标记是否启用刷新超时配置uint16_t flush_to; // 超时时间bool fcr_present; // 标记是否启用 FCR 协商tL2CAP_FCR_OPTS fcr; // 标记是否启用 FCS 配置bool fcs_present; /* Optionally bypasses FCS checks */uint8_t fcs;      /* '0' if desire is to bypass FCS, otherwise '1' */bool ext_flow_spec_present; // 标记是否启用扩展流量规格tHCI_EXT_FLOW_SPEC ext_flow_spec;uint16_t flags; /* bit 0: 0-no continuation, 1-continuation */
} tL2CAP_CFG_INFO;

L2CAP模块的配置参数结构体,用于在 L2CAP 连接建立或配置阶段协商双方的通信能力。通过可选参数标记(_present 布尔字段)灵活支持不同场景的配置需求,避免传输冗余数据。

  • 对于简单业务(如普通数据传输),仅需协商 MTU 即可;

  • 对于复杂业务(如实时音视频),可进一步协商 QoS、FCR 等参数,保障传输质量。

FLOW_SPEC

packages/modules/Bluetooth/system/stack/include/l2c_api.h
typedef struct {// QoS 控制标志位,用于标记 QoS 参数的扩展选项或特定行为uint8_t qos_flags;          /* TBD */// 定义流量的服务类型,指示数据传输的优先级或服务等级uint8_t service_type;       /* see below */// 定义令牌桶算法中令牌生成的平均速率(单位:字节 / 秒),用于控制长期平均数据传输速率uint32_t token_rate;        /* bytes/second */// 定义令牌桶的最大容量(单位:字节),表示允许的最大突发数据量uint32_t token_bucket_size; /* bytes */// 定义允许的最大瞬时数据传输速率(单位:字节 / 秒),限制短时间内的流量峰值uint32_t peak_bandwidth;    /* bytes/second */// 定义数据从发送端到接收端的最大允许延迟(单位:微秒),适用于实时业务(如语音通话)uint32_t latency;           /* microseconds */// 定义延迟的最大波动范围(单位:微秒),即相邻数据包的延迟差异uint32_t delay_variation;   /* microseconds */
} FLOW_SPEC;

L2CAP模块用于定义 QoS(服务质量)参数的结构体,主要在 L2CAP 连接建立或配置阶段协商双方的流量特性,确保数据传输满足业务的性能需求(如带宽、延迟、抖动等)。这是蓝牙支持多业务(如音频、视频、传感器数据)共存的核心机制之一。

tL2CAP_FCR_OPTS

packages/modules/Bluetooth/system/stack/include/l2c_api.h
typedef struct {
#define L2CAP_FCR_BASIC_MODE 0x00 // 基本模式
#define L2CAP_FCR_ERTM_MODE 0x03 // 增强重传模式(Enhanced Retransmission Mode)
#define L2CAP_FCR_LE_COC_MODE 0x05 // LE 连接导向通道模式(LE Connection-Oriented Channels)uint8_t mode;uint8_t tx_win_sz; // 定义发送端在未收到确认(ACK)前可连续发送的最大数据包数量(滑动窗口大小)uint8_t max_transmit; // 当数据包未收到 ACK 时,允许的最大重传次数。超过此次数后,链路将断开uint16_t rtrans_tout; // 发送端在未收到 ACK 时,等待重传的超时时间(单位:毫秒)。超时后触发重传uint16_t mon_tout; // 接收端在未收到任何数据包(包括数据或控制帧)时,判定链路失效的超时时间(单位:毫秒)uint16_t mps; // 定义单个 L2CAP 协议数据单元(PDU)的最大字节数。超过此大小的数据将被分片传输
} tL2CAP_FCR_OPTS;

配置 流量控制与重传(Flow Control and Retransmission, FCR)参数。FCR 机制是 L2CAP 的关键功能之一,通过配置不同的传输模式(如基本模式、增强重传模式)和参数(如窗口大小、超时时间),实现对数据传输可靠性与效率的平衡。

不同传输模式下,部分参数的有效性不同:

模式tx_win_szmax_transmitrtrans_tout(Retransmission Timeout)mon_tout(Monitoring Timeout)mps(Maximum Protocol Data Unit Size)
基本模式(0x00)无意义无意义无意义无意义有效
增强重传模式(0x03)有效有效有效有效有效
LE COC 模式(0x05)有效无意义无意义有效有效

实际应用场景

  • BLE 传感器数据传输:使用 LE COC 模式(L2CAP_FCR_LE_COC_MODE),配置较小的 mps(如 20 字节)和较长的 mon_tout(如 1000ms),平衡低功耗与链路可靠性。

  • 蓝牙文件传输:使用 ERTM 模式(L2CAP_FCR_ERTM_MODE),增大 tx_win_sz(如 4)和 max_transmit(如 3),并调整 rtrans_tout 匹配链路 RTT,提升吞吐量和可靠性。

  • 实时音频流:使用基本模式(L2CAP_FCR_BASIC_MODE),忽略重传参数,减少协议开销,确保低延迟。

模式说明:

  • 基本模式(L2CAP_FCR_BASIC_MODE:无流量控制和重传机制,数据按 “尽力而为” 方式传输。适用于对延迟敏感但可靠性要求低的场景(如实时音频流)。

  • 增强重传模式(L2CAP_FCR_ERTM_MODE:支持流量控制(滑动窗口)和自动重传(ARQ),确保数据可靠传输。适用于对可靠性要求高的场景(如文件传输、命令控制)。

  • LE 连接导向通道模式(L2CAP_FCR_LE_COC_MODE:专为低功耗蓝牙(BLE)设计的连接导向模式,优化了低功耗场景下的周期性数据传输(如传感器数据上报)。

tHCI_EXT_FLOW_SPEC

packages/modules/Bluetooth/system/stack/include/hcidefs.h
/* Define the extended flow specification fields used by AMP */
typedef struct {uint8_t id;uint8_t stype; // 指示流量的服务类型,定义数据传输的优先级和 QoS 要求uint16_t max_sdu_size; // 定义上层协议(如 L2CAP、GATT)传递给 HCI 层的单个服务数据单元(SDU)的最大字节数uint32_t sdu_inter_time; // 指示连续两个 SDU 之间的最小时间间隔(单位:微秒,μs),用于控制数据发送速率uint32_t access_latency; // 定义数据从上层提交到 HCI 层,到控制器实际开始传输该数据的最大允许时间(单位:微秒)uint32_t flush_timeout; // 定义数据在控制器发送缓冲区中未被传输的最大时间(单位:微秒)。超时后,控制器将丢弃该数据以释放缓冲区
} tHCI_EXT_FLOW_SPEC;

HCI层定义的扩展流量规范结构体,主要用于 AMP(Alternate MAC/PHY)场景下的流量管理。AMP 允许蓝牙设备通过不同的 MAC/PHY 层(如经典蓝牙 BR/EDR、低功耗蓝牙 LE)传输数据,而该结构体用于配置流量的服务质量(QoS)参数,确保数据传输的可靠性、延迟和带宽符合业务需求。

当使用 AMP 技术时,需要为不同的数据流(如音频、视频、传感器数据)定义具体的流量参数,以便控制器分配资源(如带宽、缓冲区)并优化传输策略。

tL2CAP_CFG_INFO

/* Define a structure to hold the configuration parameters. Since the* parameters are optional, for each parameter there is a boolean to* use to signify its presence or absence.*/
typedef struct {uint16_t result; /* Only used in confirm messages */bool mtu_present; // 标识是否协商 L2CAP 最大传输单元(MTU)uint16_t mtu;bool qos_present; // 标识是否协商服务质量(QoS)参数FLOW_SPEC qos;bool flush_to_present; // 标识是否协商刷新超时(Flush Timeout)uint16_t flush_to; //  表示数据在链路缓冲区中未被传输的最大时间(单位:基础时间单位,通常为 1.25ms)bool fcr_present; // 标识是否协商流量控制与重传(FCR)策略tL2CAP_FCR_OPTS fcr;bool fcs_present; /* Optionally bypasses FCS checks */uint8_t fcs;      /* '0' if desire is to bypass FCS, otherwise '1' */bool ext_flow_spec_present; // 标识是否协商扩展流量规范(仅在 AMP,Alternate MAC/PHY 场景下有效)tHCI_EXT_FLOW_SPEC ext_flow_spec;uint16_t flags; /* bit 0: 0-no continuation, 1-continuation */
} tL2CAP_CFG_INFO;

L2CAP层的配置参数结构体,用于在 L2CAP 链路建立或更新时协商可选的配置参数。L2CAP 链路建立时,两端设备需要协商一系列参数(如最大传输单元、流量控制策略、服务质量等)以优化数据传输。由于不同设备支持的特性可能不同(例如,经典蓝牙与低功耗蓝牙的能力差异),L2CAP 协议允许协商可选参数,tL2CAP_CFG_INFO 即为协议栈内部用于存储这些可选参数的容器。

tCONN_CB

packages/modules/Bluetooth/system/stack/sdp/sdpint.h
/* The maximum number of record handles retrieved in a search. */
#ifndef SDP_MAX_DISC_SERVER_RECS
#define SDP_MAX_DISC_SERVER_RECS 21
#endif/* Define the SDP Connection Control Block */
struct tCONN_CB {uint8_t con_state;uint8_t con_flags; // 连接标志位(如是否加密、是否认证,或自定义状态标志)RawAddress device_address;alarm_t* sdp_conn_timer;uint16_t rem_mtu_size;uint16_t connection_id;uint16_t list_len; /* length of the response in the GKI buffer */uint16_t pse_dynamic_attributes_len; /* length of the attributes need to beadded in final sdp response len */uint8_t* rsp_list; /* pointer to GKI buffer holding response */tSDP_DISCOVERY_DB* p_db; /* Database to save info into   */tSDP_DISC_CMPL_CB* p_cb; /* Callback for discovery done  */tSDP_DISC_CMPL_CB2*p_cb2; /* Callback for discovery done piggy back with the user data */const void* user_data; /* piggy back user data */uint32_thandles[SDP_MAX_DISC_SERVER_RECS]; /* Discovered server record handles */uint16_t num_handles;                  /* Number of server handles     */uint16_t cur_handle;                   /* Current handle being processed */uint16_t transaction_id;uint16_t disconnect_reason; /* Disconnect reason            */uint8_t disc_state; // 服务发现过程的状态bool is_attr_search; // 是否为属性搜索uint16_t cont_offset;     /* Continuation state data in the server response */tSDP_CONT_INFO cont_info; /* structure to hold continuation information forthe server response */tCONN_CB() = default;private:tCONN_CB(const tCONN_CB&) = delete;
};

struct tCONN_CB 是蓝牙 SDP模块中的连接控制块(Connection Control Block),用于管理 SDP 连接的全生命周期(建立、数据传输、断开),并存储连接过程中的状态、数据和回调信息。

典型工作流程

假设设备 A 发起 SDP 服务发现请求,tCONN_CB 的关键交互如下:

①连接建立

  • con_state 设为 SDP_CONN_OPENING,启动 sdp_conn_timer 监控连接超时。

  • 与设备 B 协商 rem_mtu_size,确定单次传输的最大数据量。

②服务搜索

  • 设备 B 返回服务记录句柄列表,存储到 handles[]num_handles 记录实际数量。

  • 若句柄数量超过 SDP_MAX_DISC_SERVER_RECS,通过 cont_offsetcont_info 分段传输。

③属性获取is_attr_search 设为 true,逐个处理 cur_handle 对应的服务属性,数据缓存到 rsp_list

④发现完成:所有句柄处理完成后,调用 p_cbp_cb2 通知上层,传递 p_db 中的服务发现结果。

⑤连接断开con_state 设为 SDP_CONN_CLOSING,记录 disconnect_reason(如正常断开或超时),释放 sdp_conn_timerrsp_list 缓冲区。

tSDP_DISCOVERY_DB

packages/modules/Bluetooth/system/stack/sdp/sdp_discovery_db.h
// 表示一条具体的服务记录(Service Record),用于存储从对端设备发现的服务的详细信息(如服务类型、支持的协议、属性列表等)
typedef struct t_sdp_disc_rec {tSDP_DISC_ATTR* p_first_attr;      /* First attribute of record    */struct t_sdp_disc_rec* p_next_rec; /* Addr of next linked record   */uint32_t time_read;                /* The time the record was read */RawAddress remote_bd_addr;         /* Remote BD address            */
} tSDP_DISC_REC;// 管理所有发现的服务记录(tSDP_DISC_REC),并支持内存管理、过滤条件配置和原始数据缓存
typedef struct {uint32_t mem_size;          /* Memory size of the DB        */uint32_t mem_free;          /* Memory still available       */tSDP_DISC_REC* p_first_rec; /* Addr of first record in DB   */uint16_t num_uuid_filters;  /* Number of UUIds to filter    */bluetooth::Uuid uuid_filters[SDP_MAX_UUID_FILTERS]; /* UUIDs to filter */uint16_t num_attr_filters; /* Number of attribute filters  */uint16_t attr_filters[SDP_MAX_ATTR_FILTERS]; /* Attributes to filter */uint8_t* p_free_mem; /* Pointer to free memory       */uint8_t*raw_data; /* Received record from server. allocated/released by client  */uint32_t raw_size; /* size of raw_data */uint32_t raw_used; /* length of raw_data used */
} tSDP_DISCOVERY_DB;
  • tSDP_DISC_REC 作为单个服务记录的载体,存储服务的核心属性和元数据;

  • tSDP_DISCOVERY_DB 作为数据库,管理所有记录,支持过滤、检索和内存优化。

典型工作流程

假设手机(主机)通过 SDP 发现耳机(从机)的服务,tSDP_DISCOVERY_DB 的工作流程如下:

  1. 初始化数据库: 手机协议栈初始化 tSDP_DISCOVERY_DB,分配内存(如 mem_size = 4096 字节),设置 p_free_mem 指向内存起始地址,mem_free = 4096

  2. 配置过滤条件: 手机上层应用请求发现 “音频服务”,协议栈配置 uuid_filters 为音频服务 UUID(如 0x110A),num_uuid_filters = 1;同时配置 attr_filters 为服务名称属性(0x0100)和协议描述符列表(0x0004),num_attr_filters = 2

  3. 接收并存储服务记录

耳机返回 SDP 响应,包含一条音频服务记录:

  • 原始数据(如 raw_data 存储二进制格式的服务记录);

  • 解析后生成 tSDP_DISC_REC 结构体,通过 p_first_rec 加入数据库链表;

  • 扣除已使用的内存(mem_free -= sizeof(tSDP_DISC_REC) + 属性列表大小)。

  1. 检索服务: 上层应用查询音频服务时,协议栈遍历 p_first_rec 链表,检查 remote_bd_addr(匹配耳机地址)和 uuid_filters(匹配音频 UUID),返回符合条件的 tSDP_DISC_REC 及其 p_first_attr 指向的属性列表(如服务名称 “Headphones”)。

  2. 内存回收与过期: mem_free 不足时,协议栈根据 time_read 淘汰旧记录(如超过 30 分钟未更新的记录),释放内存并更新 p_free_memmem_free

tSDP_DISC_CMPL_CB

packages/modules/Bluetooth/system/stack/include/sdp_callback.h
/* Define a callback function for when discovery is complete. */
// 基础回调接口,用于上层应用接收服务发现的最终结果(设备地址 + 状态码)
typedef void(tSDP_DISC_CMPL_CB)(const RawAddress& bd_addr, tSDP_RESULT result);
// 扩展回调接口,支持传递用户自定义数据,解决基础接口无法关联上下文的问题(例如,上层同时发起多个发现请求时,通过 user_data 区分不同请求的结果)
typedef void(tSDP_DISC_CMPL_CB2)(const RawAddress& bd_addr, tSDP_RESULT result,const void* user_data);packages/modules/Bluetooth/system/stack/include/sdp_status.h
/* Success code and error codes */
typedef enum : uint16_t {SDP_SUCCESS = 0x0000,SDP_INVALID_VERSION = 0x0001,SDP_INVALID_SERV_REC_HDL = 0x0002,SDP_INVALID_REQ_SYNTAX = 0x0003,SDP_INVALID_PDU_SIZE = 0x0004,SDP_INVALID_CONT_STATE = 0x0005,SDP_NO_RESOURCES = 0x0006,SDP_DI_REG_FAILED = 0x0007,SDP_DI_DISC_FAILED = 0x0008,SDP_NO_DI_RECORD_FOUND = 0x0009,SDP_ERR_ATTR_NOT_PRESENT = 0x000A,SDP_ILLEGAL_PARAMETER = 0x000B,HID_SDP_NO_SERV_UUID = (SDP_ILLEGAL_PARAMETER + 1),HID_SDP_MANDATORY_MISSING,SDP_NO_RECS_MATCH = 0xFFF0,SDP_CONN_FAILED = 0xFFF1,SDP_CFG_FAILED = 0xFFF2,SDP_GENERIC_ERROR = 0xFFF3,SDP_DB_FULL = 0xFFF4,SDP_CANCEL = 0xFFF8,
} tSDP_STATUS;
using tSDP_RESULT = tSDP_STATUS;

回调与状态码的协同工作

SDP 模块的典型流程中,回调与状态码的交互如下:

①上层应用通过 sdp_start_discovery() 发起服务发现,注册回调函数(如 tSDP_DISC_CMPL_CB2)并传入 user_data(如请求 ID)。

②SDP 模块与对端设备交互,执行发现操作(搜索服务记录、获取属性)。

③若发现完成(成功或失败),SDP 模块调用注册的回调函数,传递:

  • 对端设备地址(bd_addr);

  • 最终状态(result,如 SDP_SUCCESSSDP_CONN_FAILED);

  • 用户自定义数据(user_data,如请求 ID)。

④上层应用根据 result 状态码处理结果(如显示服务列表或提示连接失败)。

tSDP_CONT_INFO

packages/modules/Bluetooth/system/stack/sdp/sdpint.h
/* Continuation information for the SDP server response */
typedef struct {uint16_t next_attr_index;    /* attr index for next continuation response */uint16_t next_attr_start_id; /* attr id to start with for the attr index innext cont. response */const tSDP_RECORD* prev_sdp_rec; /* last sdp record that was completely sentin the response */bool last_attr_seq_desc_sent; /* whether attr seq length has been sentpreviously */uint16_t attr_offset; /* offset within the attr to keep trak of partialattributes in the responses */
} tSDP_CONT_INFO;

SDP 协议中,当服务记录(Service Record)或属性列表(Attribute List)的大小超过单次传输的最大 PDU(协议数据单元)长度(由 MTU 决定)时,服务器会将响应分段传输。tSDP_CONT_INFO 用于存储分段传输的上下文信息(如当前处理的属性位置、已发送的记录标识),确保:

  • 接收方能正确拼接多段响应,避免数据丢失或乱序;

  • 服务器能准确续传剩余数据,减少重复传输;

  • 协议实现兼容不同设备的 MTU 限制(如经典蓝牙与低功耗蓝牙的 MTU 差异)。

tSDP_DB

packages/modules/Bluetooth/system/stack/sdp/sdpint.h
/* Define the SDP database */
typedef struct {uint32_t// 设备标识主记录句柄di_primary_handle; /* Device ID Primary record or NULL if nonexistent */uint16_t num_records;tSDP_RECORD record[SDP_MAX_RECORDS]; // 存储本地设备发布的所有服务记录
} tSDP_DB;

在蓝牙通信中,支持 SDP 协议的设备通常扮演两种角色:

  • SDP 客户端:主动查询对端设备的服务(如手机搜索耳机的音频服务);

  • SDP 服务器:被动响应查询,提供本地服务记录(如耳机发布自身支持的音频服务)。

struct tSDP_DB 即为 SDP 服务器的 “服务仓库”,存储本地设备所有可被发现的服务记录,是设备对外提供服务发现能力的基础。

Device ID 记录:是 SDP 中一类特殊的服务记录,用于描述设备的基本信息(如设备类型、制造商、型号),遵循蓝牙 SIG 定义的 Device ID 配置文件(Device ID Profile)。例如,耳机的 Device ID 记录可能包含制造商 ID(如 0x004C 对应 Apple)和产品型号。

tSDP_RECORD

packages/modules/Bluetooth/system/stack/sdp/sdpint.h
// 表示 SDP 服务记录中的单个属性(Attribute),用于描述服务的具体特征(如服务名称、支持的协议、端口号等)。SDP 协议通过属性实现对服务的精细化描述,而 tSDP_ATTRIBUTE 是这一描述的最小存储单元
/* Define the attribute element of the SDP database record */
typedef struct {uint32_t len;       /* Number of bytes in the entry */uint8_t* value_ptr; /* Points to attr_pad */uint16_t id;uint8_t type; // 属性值的数据类型(由 SDP 规范定义)
} tSDP_ATTRIBUTE;// 表示一条完整的服务记录(Service Record),是 SDP 服务发现的核心实体。每条记录对应一个具体的服务(如音频传输服务、文件传输服务),由唯一的句柄标识,并包含多个属性(tSDP_ATTRIBUTE)
/* An SDP record consists of a handle, and 1 or more attributes */
typedef struct {uint32_t record_handle; // 服务记录句柄(由 SDP 服务器分配的唯一标识符),用于快速定位记录uint32_t free_pad_ptr; // attr_pad 数组中当前空闲位置的偏移量(字节)。用于内联存储属性值时的动态分配(例如,第一个属性使用 attr_pad[0~31],则 free_pad_ptr 设为 32)uint16_t num_attributes; // 当前记录包含的属性数量tSDP_ATTRIBUTE attribute[SDP_MAX_REC_ATTR];uint8_t attr_pad[SDP_MAX_PAD_LEN]; // 内联存储属性值的缓冲区(预分配的连续内存)。仅用于存储长度 ≤ SDP_MAX_PAD_LEN 的属性值
} tSDP_RECORD;

数据库的典型操作

  • 添加服务记录:设备启动时,将预定义的服务(如音频服务、HID 服务)封装为 tSDP_RECORD,并添加到 record 数组,num_records 递增。

  • 响应查询请求:当其他设备发起 SDP 查询(如搜索音频服务),SDP 服务器遍历 record 数组,匹配服务 UUID(通过 tSDP_ATTRIBUTEid=0x0001 属性),返回符合条件的记录及其属性。

  • 更新服务记录:若服务属性变化(如修改服务名称),找到对应的 tSDP_RECORD,更新其 attribute 数组中的相应 tSDP_ATTRIBUTE(可能需要调整 free_pad_ptr 或重新分配 value_ptr)。

三级结构的协同关系:

tSDP_ATTRIBUTEtSDP_RECORDtSDP_DB 构成了 SDP 服务信息的三级存储体系:

  1. 属性层(tSDP_ATTRIBUTE):最小单元,存储服务的具体特征(如名称、协议)。

  2. 记录层(tSDP_RECORD):容器,将多个属性封装为一条完整的服务记录(如音频服务)。

  3. 数据库层(tSDP_DB):仓库,管理所有服务记录,支持设备对外发布服务。

服务记录的构建示例

假设构建一个 “音频传输服务” 的 tSDP_RECORD

tSDP_RECORD audio_record = {.record_handle = 0x00000001,  // 记录句柄.num_attributes = 3,          // 包含 3 个属性.attribute = {[0] = {  // 服务类 UUID(属性 ID 0x0001).id = 0x0001,.type = 0x0A,         // UUID 类型(128 位).len = 16,            // 128 位 UUID 占 16 字节.value_ptr = &audio_record.attr_pad[0]  // 内联存储到 attr_pad},[1] = {  // 服务名称(属性 ID 0x0100).id = 0x0100,.type = 0x05,         // 字符串类型.len = 12,            // "Headphones" 占 12 字节.value_ptr = &audio_record.attr_pad[16] // 内联存储到 attr_pad},[2] = {  // 协议描述符列表(属性 ID 0x0004).id = 0x0004,.type = 0x35,         // 序列类型(包含多个协议描述符).len = 8,             // 序列长度 8 字节.value_ptr = &audio_record.attr_pad[28] // 内联存储到 attr_pad}},.free_pad_ptr = 36  // attr_pad 已使用 36 字节(16+12+8)
};
// 内联存储的属性值直接写入 attr_pad
memcpy(audio_record.attr_pad, audio_service_uuid, 16);       // 音频服务 UUID
memcpy(audio_record.attr_pad + 16, "Headphones", 12);        // 服务名称
memcpy(audio_record.attr_pad + 28, protocol_descriptor, 8);  // 协议描述符列表

tL2CAP_APPL_INFO

packages/modules/Bluetooth/system/stack/include/l2c_api.h
/* Connection indication callback prototype. Parameters are*              BD Address of remote*              Local CID assigned to the connection*              PSM that the remote wants to connect to*              Identifier that the remote sent*/
typedef void(tL2CA_CONNECT_IND_CB)(const RawAddress&, uint16_t, uint16_t,uint8_t);/* Connection confirmation callback prototype. Parameters are*              Local CID*              Result - 0 = connected*              If there is an error, tL2CA_ERROR_CB is invoked*/
typedef void(tL2CA_CONNECT_CFM_CB)(uint16_t, uint16_t);/* Configuration indication callback prototype. Parameters are*              Local CID assigned to the connection*              Pointer to configuration info*/
typedef void(tL2CA_CONFIG_IND_CB)(uint16_t, tL2CAP_CFG_INFO*);/* Configuration confirm callback prototype. Parameters are*              Local CID assigned to the connection*              Initiator (1 for local, 0 for remote)*              Initial config from remote* If there is an error, tL2CA_ERROR_CB is invoked*/
typedef void(tL2CA_CONFIG_CFM_CB)(uint16_t, uint16_t, tL2CAP_CFG_INFO*);/* Disconnect indication callback prototype. Parameters are*              Local CID*              Boolean whether upper layer should ack this*/
typedef void(tL2CA_DISCONNECT_IND_CB)(uint16_t, bool);/* Disconnect confirm callback prototype. Parameters are*              Local CID*              Result*/
typedef void(tL2CA_DISCONNECT_CFM_CB)(uint16_t, uint16_t);/* Disconnect confirm callback prototype. Parameters are*              Local CID*              Result*/
typedef void(tL2CA_DATA_IND_CB)(uint16_t, BT_HDR*);/* Congestion status callback protype. This callback is optional. If* an application tries to send data when the transmit queue is full,* the data will anyways be dropped. The parameter is:*              Local CID*              true if congested, false if uncongested*/
typedef void(tL2CA_CONGESTION_STATUS_CB)(uint16_t, bool);/* Transmit complete callback protype. This callback is optional. If* set, L2CAP will call it when packets are sent or flushed. If the* count is 0xFFFF, it means all packets are sent for that CID (eRTM* mode only). The parameters are:*              Local CID*              Number of SDUs sent or dropped*/
typedef void(tL2CA_TX_COMPLETE_CB)(uint16_t, uint16_t);/** Notify the user when the remote send error result on ConnectRsp or ConfigRsp* The parameters are:*              Local CID*              Error type (L2CAP_CONN_OTHER_ERROR for ConnectRsp,*                          L2CAP_CFG_FAILED_NO_REASON for ConfigRsp)*/
typedef void(tL2CA_ERROR_CB)(uint16_t, uint16_t);/* Create credit based connection request callback prototype. Parameters are*              BD Address of remote*              Vector of allocated local cids to accept*              PSM*              Peer MTU*              Identifier that the remote sent*/
typedef void(tL2CA_CREDIT_BASED_CONNECT_IND_CB)(const RawAddress& bdaddr,std::vector<uint16_t>& lcids,uint16_t psm, uint16_t peer_mtu,uint8_t identifier);/* Define the structure that applications use to register with* L2CAP. This structure includes callback functions. All functions* MUST be provided, with the exception of the "connect pending"* callback and "congestion status" callback.*/
typedef struct {tL2CA_CONNECT_IND_CB* pL2CA_ConnectInd_Cb; // 连接指示回调(处理远程连接请求)tL2CA_CONNECT_CFM_CB* pL2CA_ConnectCfm_Cb; // 连接确认回调(处理本地连接结果)tL2CA_CONFIG_IND_CB* pL2CA_ConfigInd_Cb; // 配置指示回调(处理远程配置请求)tL2CA_CONFIG_CFM_CB* pL2CA_ConfigCfm_Cb; // 配置确认回调(处理本地配置结果)tL2CA_DISCONNECT_IND_CB* pL2CA_DisconnectInd_Cb; // 断开指示回调(处理远程断开请求)tL2CA_DISCONNECT_CFM_CB* pL2CA_DisconnectCfm_Cb; // 断开确认回调(处理本地断开结果)tL2CA_DATA_IND_CB* pL2CA_DataInd_Cb; // 数据指示回调(处理接收数据)tL2CA_CONGESTION_STATUS_CB* pL2CA_CongestionStatus_Cb; // 拥塞状态回调(监控发送队列状态)tL2CA_TX_COMPLETE_CB* pL2CA_TxComplete_Cb; // 传输完成回调(监控发送结果)tL2CA_ERROR_CB* pL2CA_Error_Cb; // 错误通知回调(处理连接 / 配置错误)tL2CA_CREDIT_BASED_CONNECT_IND_CB* pL2CA_CreditBasedConnectInd_Cb; // 信用基连接指示回调(LE COC 场景)tL2CA_CREDIT_BASED_CONNECT_CFM_CB* pL2CA_CreditBasedConnectCfm_Cb; // 信用基连接确认回调(LE COC 场景)tL2CA_CREDIT_BASED_RECONFIG_COMPLETED_CB*pL2CA_CreditBasedReconfigCompleted_Cb; // 信用基重配置完成回调(LE COC 场景)tL2CA_CREDIT_BASED_COLLISION_IND_CB* pL2CA_CreditBasedCollisionInd_Cb; // 信用基冲突指示回调(LE COC 场景)
} tL2CAP_APPL_INFO;

L2CAP 的核心功能包括连接管理、配置协商、数据传输、断开处理等,每个功能对应一组回调函数,用于通知上层应用关键事件。上层应用通过注册这些回调,深度参与 L2CAP 链路的全生命周期管理(连接、配置、传输、断开)。

典型交互流程示例

以手机与耳机建立 L2CAP 音频链路为例,回调的触发顺序如下:

①连接建立

  • 手机应用发起连接请求(指定 PSM 为音频服务)。

  • 耳机 L2CAP 层收到请求,触发 pL2CA_ConnectInd_Cb(连接指示回调),耳机应用检查 PSM 后接受。

  • 手机 L2CAP 层完成连接,触发 pL2CA_ConnectCfm_Cb(连接确认回调),手机应用确认连接成功。

②配置协商

  • 手机 L2CAP 层发起 MTU 协商(如请求 MTU=512),触发 pL2CA_ConfigInd_Cb(配置指示回调)。

  • 耳机应用响应配置(支持 MTU=512),触发 pL2CA_ConfigCfm_Cb(配置确认回调),双方确认 MTU。

③数据传输

  • 耳机发送音频数据,手机 L2CAP 层触发 pL2CA_DataInd_Cb(数据指示回调),手机应用提取音频数据播放。

  • 若手机发送队列满,触发 pL2CA_CongestionStatus_Cb(拥塞状态回调),手机应用暂停发送以避免丢包。

④断开连接

  • 手机应用发起断开请求,耳机 L2CAP 层触发 pL2CA_DisconnectInd_Cb(断开指示回调),耳机应用释放资源。

  • 手机 L2CAP 层完成断开,触发 pL2CA_DisconnectCfm_Cb(断开确认回调),手机应用确认链路关闭。

四、SDP 初始化流程图

五、SDP 服务发现时序图

六、总结

SDP 模块的初始化(sdp_init)是蓝牙服务发现功能的基础,通过控制块初始化、定时器创建、L2CAP 参数配置及回调注册,为 SDP 模块的运行奠定环境基础。核心数据结构(如tSDP_CBtL2CAP_CFG_INFO)通过分层设计(全局控制 - 连接管理 - 协议交互),实现了状态管理、连接控制与协议协同的高效整合。理解这些初始化细节与数据结构的协作逻辑,是掌握蓝牙服务发现底层机制、解决实际开发中连接异常(如超时、数据乱序)及性能优化(如 MTU 调优)问题的关键。


相关文章:

  • 帝可得- 人员管理
  • Linux系统-基本指令(5)
  • STM32入门教程——按键控制LED光敏传感器控制蜂鸣器
  • 05 APP 自动化- Appium 单点触控 多点触控
  • 接口自动化测试之pytest 运行方式及前置后置封装
  • 不连网也能跑大模型?
  • YAML文件
  • NLP学习路线图(二十):FastText
  • Python Pytest
  • Read View在MVCC里如何工作
  • 第二章 2.TCP IP Protocol Suite(CCNA)
  • 使用cmd命令行创建数据库和表-简单步骤记录
  • 【Zephyr 系列 6】使用 Zephyr + BLE 打造蓝牙广播与连接系统(STEVAL-IDB011V1 实战)
  • 北京通用人工智能研究院-通才智能体 LEO
  • 【Pandas】pandas DataFrame rename_axis
  • 记录被mybatis一级缓存坑的问题
  • electron-vite_18桌面共享
  • Web3如何重塑数据隐私的未来
  • LeetCode[404]左叶子之和
  • 机器学习——主成分分析(PCA)
  • 网站各页面/国外seo
  • 邯郸做wap网站的公司/搜索引擎营销的特点有
  • 做自行车网站应该注意什么/竞价网站推广
  • 做.net网站流程/网站制作工具有哪些
  • 淘宝客做网站怎么做/百度竞价登陆
  • 在哪里做企业网站/友情链接吧