服务发现协议(SDP)
本笔记为作者再学习蓝牙Host协议栈的一些心得体会,如有不对的地方,请包含与谅解!
------------by wsoz
服务发现协议(SDP)
SDP协议是在蓝牙连接以后,查询对方支持哪些服务(如A2DP音频、HFP通话、文件传输等)以及如何连接这些服务。
客户端和服务器模式
SDP协议是基于**客户端-服务器(Client-Server)**模式:

角色定义:
| 角色 | 说明 |
|---|---|
| SDP Server | 服务提供方,维护本地的服务记录数据库,响应查询请求 |
| SDP Client | 服务查询方,向Server发起查询,获取服务信息 |
工作流程:
SDP Client SDP Server
| |
| 1. 建立L2CAP连接 (PSM=0x0001) |
|--------- L2CAP Connect ----------->|
|<-------- L2CAP Connect Response ---|
| |
| 2. 发送SDP查询请求 |
|--------- SDP Request ------------->|
| |
| 3. 返回服务信息 |
|<-------- SDP Response -------------|
| |
| 4. 断开L2CAP连接 |
|--------- L2CAP Disconnect -------->|
关键特点:
- 一问一答:Client发请求,Server返回响应,典型的请求-响应模式
- 角色可互换 :同一个设备可以同时作为SDP Client和SDP Server
- 作为Server:维护自己提供的服务列表,等待别人查询
- 作为Client:主动查询其他设备的服务
- 使用固定PSM :SDP固定使用L2CAP的PSM=0x0001
- 无需认证:SDP查询通常在配对之前就可以进行
典型场景:
手机(Client) 蓝牙耳机(Server)
| |
| "你支持哪些音频服务?" |
|--- SDP查询 (A2DP UUID) ------------>|
| |
|<-- 返回:支持A2DP Sink,PSM=0x19 ---|
| |
| 现在知道了,建立A2DP连接 |
|--- L2CAP Connect (PSM=0x19) ------->|
服务记录
服务记录(Service Record) 是SDP的核心数据结构,描述一个服务的所有信息。每个蓝牙设备的SDP Server维护一个服务记录数据库,存放本机提供的所有服务。
服务记录的组成:
┌─────────────────────────────────────────┐
│ Service Record │
│ ┌───────────────────────────────────┐ │
│ │ Service Record Handle (唯一标识) │ │
│ ├───────────────────────────────────┤ │
│ │ Attribute 1 (ID + Value) │ │
│ ├───────────────────────────────────┤ │
│ │ Attribute 2 (ID + Value) │ │
│ ├───────────────────────────────────┤ │
│ │ Attribute 3 (ID + Value) │ │
│ ├───────────────────────────────────┤ │
│ │ ... │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
- Service Record Handle:服务记录句柄,32位整数,在本机唯一标识一条服务记录
- Attribute:服务属性,由属性ID和属性值组成,描述服务的具体信息
结构图

服务属性
服务属性(Service Attribute) 是服务记录中的具体信息项,每个属性由属性ID 和属性值组成。
属性结构:
+----------------+------------------+
| Attribute ID | Attribute Value |
| 16bit | 可变 |
+----------------+------------------+
常用属性ID(Universal Attributes):
| Attribute ID | 名称 | 说明 |
|---|---|---|
| 0x0000 | ServiceRecordHandle | 服务记录句柄 |
| 0x0001 | ServiceClassIDList | 服务类别UUID列表,标识这是什么服务 |
| 0x0002 | ServiceRecordState | 服务记录状态 |
| 0x0003 | ServiceID | 服务的唯一标识UUID |
| 0x0004 | ProtocolDescriptorList | 协议描述符列表,描述如何连接该服务 |
| 0x0005 | BrowseGroupList | 浏览组列表 |
| 0x0006 | LanguageBaseAttributeIDList | 语言基础属性列表 |
| 0x0009 | BluetoothProfileDescriptorList | 蓝牙Profile描述符列表 |
| 0x0100 | ServiceName | 服务名称(可读字符串) |
| 0x0101 | ServiceDescription | 服务描述 |
最重要的两个属性:
① ServiceClassIDList (0x0001) :标识这是什么类型的服务 服务类UUID
例如:A2DP Sink服务
ServiceClassIDList = [0x110B] // AudioSink UUID
② ProtocolDescriptorList (0x0004) :告诉Client如何连接这个服务 协议类UUID
例如:
ProtocolDescriptorList = [
[L2CAP, PSM=0x0019], // 先建立L2CAP连接,PSM=0x0019
[AVDTP, Version=0x0103] // 然后使用AVDTP协议
]
UUID
UUID(Universally Unique Identifier,通用唯一标识符) 用于标识服务类型、协议、Profile等。
UUID的三种形式:
| 形式 | 大小 | 说明 |
|---|---|---|
| UUID-16 | 2字节 | 蓝牙SIG定义的标准UUID,最常用 |
| UUID-32 | 4字节 | 扩展形式,较少使用 |
| UUID-128 | 16字节 | 完整形式,自定义服务使用 |
UUID-16与UUID-128的转换:
蓝牙定义了一个Base UUID:
00000000-0000-1000-8000-00805F9B34FB
UUID-16转换为UUID-128的公式:
UUID-128 = UUID-16 × 2^96 + Base UUID
例如:UUID-16 = 0x110B (AudioSink)
UUID-128 = 0000110B-0000-1000-8000-00805F9B34FB
协议UUID vs 服务类UUID:
UUID分为两大类,作用完全不同:
| 类型 | 回答的问题 | 存放位置 |
|---|---|---|
| 协议UUID | "怎么连接我?"(传输方式) | ProtocolDescriptorList (0x0004) |
| 服务类UUID | "我能做什么?"(功能类型) | ServiceClassIDList (0x0001) |
用A2DP Sink举例:
服务记录:
├── [0x0001] ServiceClassIDList = [0x110B] ← 服务类UUID
│ "我是什么" → 音频接收器(AudioSink)
│
└── [0x0004] ProtocolDescriptorList = [ ← 协议UUID
[L2CAP (0x0100), PSM=0x0019], "怎么连我" → 先建L2CAP
[AVDTP (0x0019), Version=0x0103] → 再用AVDTP传音频
]
- 服务类UUID:标识功能("我是什么")
- 协议UUID:标识传输方式("怎么用我")
两者配合才能完成:服务发现 → 服务连接 的完整流程。
常用UUID-16(协议):
| UUID-16 | 名称 | 说明 |
|---|---|---|
| 0x0001 | SDP | 服务发现协议 |
| 0x0003 | RFCOMM | 串口仿真协议 |
| 0x000F | BNEP | 蓝牙网络封装协议 |
| 0x0017 | AVCTP | 音视频控制传输协议 |
| 0x0019 | AVDTP | 音视频分发传输协议 |
| 0x0100 | L2CAP | 逻辑链路控制协议 |
常用UUID-16(服务类/Profile):
| UUID-16 | 名称 | 说明 |
|---|---|---|
| 0x1101 | SerialPort | 串口服务(SPP) |
| 0x1105 | OBEXObjectPush | 对象推送服务 |
| 0x110A | AudioSource | 音频源(A2DP Source) |
| 0x110B | AudioSink | 音频接收(A2DP Sink) |
| 0x110C | A/V RemoteControlTarget | AVRCP Target |
| 0x110E | A/V RemoteControl | AVRCP Controller |
| 0x111E | Handsfree | 免提服务(HFP) |
| 0x111F | HandsfreeAudioGateway | 免提音频网关(HFP AG) |
| 0x1124 | HumanInterfaceDevice | HID设备 |
| 0x112F | Phonebook Access PSE | 电话簿访问服务端 |
服务记录示例(A2DP Sink):
Service Record:
├── [0x0000] ServiceRecordHandle = 0x00010001
├── [0x0001] ServiceClassIDList = [0x110B] // AudioSink UUID--服务类
├── [0x0004] ProtocolDescriptorList = [
│ [L2CAP, PSM=0x0019], // L2CAP连接参数
│ [AVDTP, Version=0x0103] // AVDTP版本
│ ]
├── [0x0009] BluetoothProfileDescriptorList = [
│ [0x110D, Version=0x0103] // A2DP v1.3
│ ]
└── [0x0100] ServiceName = "Audio Sink"
服务类
服务类(Service Class) 用于标识一个服务的类型,通过UUID 来表示。在服务记录中,由ServiceClassIDList(0x0001) 属性来声明该服务属于哪些服务类。
服务类的作用:
SDP Client SDP Server
| |
| "我要找支持A2DP Sink的服务" |
|--- 查询 UUID=0x110B ----------------->|
| |
| Server检查所有服务记录的 |
| ServiceClassIDList是否包含0x110B |
| |
|<-- 返回匹配的服务记录 ----------------|
常用服务类UUID:
音频相关:
| UUID-16 | 服务类名称 | 说明 |
|---|---|---|
| 0x110A | AudioSource | A2DP音频源(如手机播放音乐) |
| 0x110B | AudioSink | A2DP音频接收(如蓝牙耳机) |
| 0x110C | A/V RemoteControlTarget | AVRCP目标端 |
| 0x110E | A/V RemoteControl | AVRCP控制端 |
| 0x110F | A/V RemoteControlController | AVRCP控制器 |
电话相关:
| UUID-16 | 服务类名称 | 说明 |
|---|---|---|
| 0x111E | Handsfree | 免提服务(HFP HF端,如车载) |
| 0x111F | HandsfreeAudioGateway | 免提音频网关(HFP AG端,如手机) |
| 0x1112 | HeadsetAudioGateway | 耳机音频网关(HSP AG端) |
| 0x1108 | Headset | 耳机服务(HSP) |
| 0x112E | PhoneBookAccessPCE | 电话簿访问客户端 |
| 0x112F | PhoneBookAccessPSE | 电话簿访问服务端 |
数据传输相关:
| UUID-16 | 服务类名称 | 说明 |
|---|---|---|
| 0x1101 | SerialPort | 串口服务(SPP) |
| 0x1105 | OBEXObjectPush | 对象推送(OPP) |
| 0x1106 | OBEXFileTransfer | 文件传输(FTP) |
| 0x1115 | PANU | 个人区域网络用户 |
| 0x1116 | NAP | 网络接入点 |
输入设备相关:
| UUID-16 | 服务类名称 | 说明 |
|---|---|---|
| 0x1124 | HumanInterfaceDevice | HID设备(键盘、鼠标等) |
| 0x1812 | HID over GATT | BLE HID设备(HOGP) |
ServiceClassIDList可以包含多个UUID:
下面讨论一下一个设备有多种功能时的UUID情况:
① 一个Service Record包含多个UUID(功能相关、共享连接):
例如:蓝牙耳机同时支持HSP和HFP
[0x0001] ServiceClassIDList = [
0x1108, // Headset(HSP耳机)
0x111E // Handsfree(HFP免提)
]
[0x0004] ProtocolDescriptorList = [[L2CAP, PSM=0x0003], [RFCOMM, Channel=1]]
// 共享同一个PSM和连接方式
② 多个独立Service Record(功能独立、PSM不同):
此时就需要我们来新开一个服务记录,因为连接的链路不同
例如:设备同时支持SPP和HID
Service Record 1: [0x0001] = [0x1101] // SPP, PSM=0x0003
Service Record 2: [0x0001] = [0x1124] // HID, PSM=0x0011
// 不同PSM,需要分别连接
服务类与Profile的关系:
| 概念 | 说明 |
|---|---|
| Service Class | 服务记录中声明的UUID,表示"我提供什么服务" |
| Profile | 蓝牙规范定义的完整功能集,描述"如何实现这个功能" |
- 一个Profile可能对应一个或多个Service Class
- 例如:A2DP Profile包含AudioSource和AudioSink两个服务类
服务类与服务记录的关系(面向对象理解):
| SDP概念 | 面向对象概念 | 说明 |
|---|---|---|
| 服务类(Service Class) | 类(Class) | 定义属性的模板 |
| 服务记录(Service Record) | 实例(Instance) | 服务类的一个具体实例 |
| 服务属性(Attribute) | 成员属性(Property) | 具体的信息项 |
- 服务类定义了该类服务应该包含哪些属性(Attribute ID、格式、用法)
- 服务记录是服务类的具体实例,包含实际的属性值
- 每个服务记录都是某个服务类的实例
服务类的继承机制:
服务类支持继承,子类继承父类的所有属性,并可定义自己特有的属性:
┌─────────────────────┐
│ 基础服务类 │ 定义通用属性
│ (所有服务共有) │ 0x0000, 0x0001, 0x0004...
└──────────┬──────────┘
│ 继承
┌──────────▼──────────┐
│ AudioSink │ 继承父类属性
│ (0x110B) │ + 音频专用属性
└──────────┬──────────┘
│ 继承
┌──────────▼──────────┐
│ AdvancedAudioSink │ 继承父类属性
│ (子类) │ + 更多专用属性
└─────────────────────┘
属性分类:
| 属性类型 | 说明 |
|---|---|
| 通用属性 | 所有服务都有的属性,如ServiceRecordHandle(0x0000)、ServiceClassIDList(0x0001) |
| 专用属性 | 特定服务类才有的属性,由该服务类定义 |
ServiceClassIDList的排列顺序:
当服务类存在继承关系时,ServiceClassIDList中的UUID按照从子类到父类的顺序排列:
[0x0001] ServiceClassIDList = [
0x110B, // 子类(最具体的) ← 排在最前面
0x1200, // 父类
... // 更高层的父类 ← 排在后面
]
- 最前面的是最具体的服务类(子类)
- 后面依次是父类、祖父类...
- 查询时,用父类UUID也能匹配到子类服务
特定类属性
特定类属性(Class-Specific Attributes) 是由特定服务类定义的属性,只在该服务类的服务记录中出现,与通用属性相对。
属性ID分配范围:
| 范围 | 类型 | 说明 |
|---|---|---|
| 0x0000 - 0x01FF | 通用属性 | 所有服务类共用,由SDP规范定义 |
| 0x0200 - 0xFFFF | 特定类属性 | 由各服务类自行定义 |
通用属性 vs 特定类属性:
服务记录:
├── [0x0000] ServiceRecordHandle ← 通用属性(所有服务都有)
├── [0x0001] ServiceClassIDList ← 通用属性
├── [0x0004] ProtocolDescriptorList ← 通用属性
├── [0x0100] ServiceName ← 通用属性
│
├── [0x0200] VersionNumberList ← 特定类属性(某些服务类定义)
├── [0x0311] SupportedFeatures ← 特定类属性(如HFP定义)
└── ...
常见特定类属性示例:
HFP(免提服务)特定属性:
| Attribute ID | 名称 | 说明 |
|---|---|---|
| 0x0311 | SupportedFeatures | 支持的特性(如三方通话、语音识别等) |
HFP服务记录示例:
├── [0x0001] ServiceClassIDList = [0x111E] // Handsfree
├── [0x0004] ProtocolDescriptorList = [...]
└── [0x0311] SupportedFeatures = 0x003F // 支持的HFP特性掩码
├── Bit0: EC/NR(回声消除/噪声抑制)
├── Bit1: 三方通话
├── Bit2: CLI(来电显示)
├── Bit3: 语音识别
├── Bit4: 远程音量控制
└── Bit5: 宽带语音
A2DP(高级音频分发)特定属性:
| Attribute ID | 名称 | 说明 |
|---|---|---|
| 0x0311 | SupportedFeatures | 支持的特性(如播放器、麦克风等) |
A2DP Sink服务记录示例:
├── [0x0001] ServiceClassIDList = [0x110B] // AudioSink
├── [0x0004] ProtocolDescriptorList = [...]
└── [0x0311] SupportedFeatures = 0x0003
├── Bit0: 耳机
└── Bit1: 扬声器
AVRCP(音视频远程控制)特定属性:
| Attribute ID | 名称 | 说明 |
|---|---|---|
| 0x0311 | SupportedFeatures | 支持的特性 |
AVRCP Controller服务记录示例:
├── [0x0001] ServiceClassIDList = [0x110E] // A/V RemoteControl
├── [0x0004] ProtocolDescriptorList = [...]
└── [0x0311] SupportedFeatures = 0x0001
└── Bit0: Category 1 (播放器/录音机)
SPP(串口服务)特定属性:
SPP服务类相对简单,主要使用通用属性,没有太多特定类属性。
特定类属性的作用:
- 能力协商:告诉对方自己支持哪些高级特性
- 版本兼容:标识支持的功能版本
- 功能裁剪:让Client知道Server具体支持什么,避免调用不支持的功能
注意:
- 相同的Attribute ID(如0x0311)在不同服务类中含义可能不同
- 解析特定类属性前,必须先确认服务类(ServiceClassIDList)
数据表示方法
SDP使用数据元素(Data Element) 来表示所有的属性值 ,这是一种TLV(Type-Length-Value) 结构。
数据元素基本结构:
+------------------+------------------+------------------+
| Header | 可选Size | Data |
| (1字节) | (0/1/2/4字节) | (可变) |
+------------------+------------------+------------------+
Header字节结构:
Bit: 7 6 5 4 3 2 1 0
+--+--+--+--+--+--+--+--+
| Type Descriptor | Size|
| (5 bit) | Idx |
+--+--+--+--+--+--+--+--+
Type Descriptor(类型描述符,5bit):
| 值 | 类型 | 说明 |
|---|---|---|
| 0 | Nil | 空值 |
| 1 | Unsigned Integer | 无符号整数 |
| 2 | Signed Integer | 有符号整数 |
| 3 | UUID | 通用唯一标识符 |
| 4 | Text String | 文本字符串 |
| 5 | Boolean | 布尔值 |
| 6 | Data Element Sequence | 数据元素序列(类似数组) |
| 7 | Data Element Alternative | 数据元素选择(多选一) |
| 8 | URL | 统一资源定位符 |
Size Index(大小索引,3bit):
| Size Index | 含义 | 附加Size字段 | 数据长度 |
|---|---|---|---|
| 0 | 1字节数据,如果type为0则无字节数据 | 无 | 1 |
| 1 | 2字节数据 | 无 | 2 |
| 2 | 4字节数据 | 无 | 4 |
| 3 | 8字节数据 | 无 | 8 |
| 4 | 16字节数据 | 无 | 16 |
| 5 | 后跟1字节Size大小 | 1字节 | 0-255 |
| 6 | 后跟2字节Size大小 | 2字节 | 0-65535 |
| 7 | 后跟4字节Size大小 | 4字节 | 0-2^32-1 |
示例解析:
① UUID-16 (0x110B):
Header: 0x19
├── Type = 3 (UUID) → 00011
└── Size Index = 1 (2字节) → 001
完整数据: 0x19 0x11 0x0B
↑ ↑────↑
Header UUID值(0x110B)
② 无符号整数 (PSM=0x0019):
Header: 0x09
├── Type = 1 (Unsigned Int) → 00001
└── Size Index = 1 (2字节) → 001
完整数据: 0x09 0x00 0x19
↑ ↑────↑
Header 值(0x0019)
③ 文本字符串 "Audio Sink":
Header: 0x25
├── Type = 4 (Text String) → 00100
└── Size Index = 5 (后跟1字节Size) → 101
完整数据: 0x25 0x0A "Audio Sink"
↑ ↑ ↑─────────↑
Header 长度10 字符串内容
④ Data Element Sequence(序列):
ServiceClassIDList = [0x110B]
Header: 0x35
├── Type = 6 (Sequence) → 00110
└── Size Index = 5 (后跟1字节Size) → 101
完整数据: 0x35 0x03 0x19 0x11 0x0B
↑ ↑ ↑─────────↑
Header 长度3 UUID元素(0x110B)
ProtocolDescriptorList完整示例:
ProtocolDescriptorList = [[L2CAP, PSM=0x0019], [AVDTP]]
结构:
Sequence [ // 外层序列
Sequence [ // L2CAP协议描述
UUID(0x0100), // L2CAP UUID
Uint16(0x0019) // PSM值
],
Sequence [ // AVDTP协议描述
UUID(0x0019) // AVDTP UUID
]
]
二进制:
0x35 0x0C // 外层Sequence, 长度12
0x35 0x06 // 内层Sequence, 长度6
0x19 0x01 0x00 // UUID: L2CAP (0x0100)
0x09 0x00 0x19 // Uint16: PSM (0x0019)
0x35 0x03 // 内层Sequence, 长度3
0x19 0x00 0x19 // UUID: AVDTP (0x0019)
总结:
| 概念 | 说明 |
|---|---|
| Data Element | SDP中所有数据的表示格式 |
| Header | Type(5bit) + Size Index(3bit) |
| 嵌套 | Sequence可以包含其他Data Element,形成嵌套结构 |
服务记录的编码格式
服务记录在二进制层面完全由Data Element格式编码,两者的关系是:
- Data Element:编码规则(怎么表示数据)
- 服务记录:用Data Element规则组织的具体内容
属性的编码结构
每一条属性在二进制中的结构是:
DES(外层容器)
├── UINT16(属性ID)
└── 属性值(基本类型 或 嵌套DES)
DES是大框架,用来把属性ID和属性值包在一起;属性值简单时用基本类型(UUID16/UINT8等),复杂时再套DES。
示例:ServiceClassIDList(0x0001)
SDP_DES_SIZE8, 0x08, // DES容器,长度8字节
SDP_UINT16, 0x00, 0x01, // 属性ID = 0x0001
SDP_DES_SIZE8, 0x03, // 属性值:DES容器,长度3字节
SDP_UUID16, 0x11, 0x24 // UUID = 0x1124(HID服务)
为什么ProtocolDescriptorList要多层嵌套
ProtocolDescriptorList描述"协议栈",每一层协议都需要独立描述,因此必须分开写:
DES(整个属性)
├── UINT16(属性ID = 0x0004)
└── DES(协议列表)
├── DES(底层:L2CAP) ← 说明"用L2CAP,监听哪个PSM"
│ ├── UUID16(L2CAP)
│ └── UINT16(PSM)
└── DES(上层:HID/AVDTP等) ← 说明"L2CAP上跑什么协议"
└── UUID16(协议UUID)
两个并列DES语义不同,不能合并:Host解析时按固定格式取PSM和上层协议,合并后无法区分。
总结:三者关系
| 概念 | 角色 | 说明 |
|---|---|---|
| Data Element | 编码规则 | 所有数据的表示格式(TLV) |
| DES(Sequence) | 结构容器 | 组织嵌套结构的框架 |
| 服务记录 | 具体内容 | 用Data Element编码的属性集合 |
简单记:服务记录 = 一堆属性;每个属性 = DES包裹(ID + 值);值复杂时 = 继续嵌套DES。
SDP协议
协议要求
传输层要求:
- SDP基于L2CAP 传输,使用固定PSM = 0x0001
- SDP使用Basic L2CAP Mode(基本模式,不使用ERTM)
- 最小MTU要求:48字节
- 大端数据传输
协议特点:
- 请求-响应模式:Client发请求,Server必须响应
- 事务ID匹配:每个请求有唯一的Transaction ID,响应必须使用相同的ID
- 支持分片:大量数据可以通过Continuation State分多次传输
- 嵌套格式:协议Data Element均是通过数据格式进行嵌套进行表示
PDU格式
SDP PDU通用结构:
+----------+----------------+------------------+------------------+
| PDU ID | Transaction ID | Parameter Length | Parameters |
| (1字节) | (2字节) | (2字节) | (可变) |
+----------+----------------+------------------+------------------+
| 字段 | 大小 | 说明 |
|---|---|---|
| PDU ID | 1字节 | PDU类型标识 |
| Transaction ID | 2字节 | 事务标识符,请求和响应必须匹配 |
| Parameter Length | 2字节 | Parameters字段的长度 |
| Parameters | 可变 | PDU的具体参数,取决于PDU类型 |
PDU类型:
| PDU ID | 名称 | 方向 | 说明 |
|---|---|---|---|
| 0x01 | SDP_ErrorResponse | S→C | 错误响应 |
| 0x02 | SDP_ServiceSearchRequest | C→S | 服务搜索请求 |
| 0x03 | SDP_ServiceSearchResponse | S→C | 服务搜索响应 |
| 0x04 | SDP_ServiceAttributeRequest | C→S | 属性查询请求 |
| 0x05 | SDP_ServiceAttributeResponse | S→C | 属性查询响应 |
| 0x06 | SDP_ServiceSearchAttributeRequest | C→S | 服务搜索+属性查询请求 |
| 0x07 | SDP_ServiceSearchAttributeResponse | S→C | 服务搜索+属性查询响应 |
各PDU详解
① SDP_ErrorResponse (0x01)
当Server无法处理请求时返回错误响应。
Parameters:
+------------+------------------+
| ErrorCode | ErrorInfo |
| (2字节) | (可变) |
+------------+------------------+
| ErrorCode | 含义 |
|---|---|
| 0x0001 | Invalid SDP Version |
| 0x0002 | Invalid Service Record Handle |
| 0x0003 | Invalid Request Syntax |
| 0x0004 | Invalid PDU Size |
| 0x0005 | Invalid Continuation State |
| 0x0006 | Insufficient Resources |
② SDP_ServiceSearchRequest (0x02)
根据UUID搜索匹配的服务,返回Service Record Handle列表。服务器会去自己的服务记录中去查询有无对应的UUID进行匹配
Parameters:
+---------------------+----------------------+--------------------+
| ServiceSearchPattern| MaxServiceRecordCount| ContinuationState |
| (Data Element) | (2字节) | (可变) |
+---------------------+----------------------+--------------------+
| 字段 | 说明 |
|---|---|
| ServiceSearchPattern | 要搜索的UUID列表(最多12个) |
| MaxServiceRecordCount | 最多返回多少条记录 |
| ContinuationState | 分片继续状态(首次请求为0x00) |
示例:搜索 A2DP Sink (UUID=0x110B)
02 00 01 00 08 35 03 19 11 0B 00 10 00
↑ ↑────↑ ↑────↑ ↑───────────↑ ↑────↑ ↑
│ │ │ │ │ └─ ContinuationState=0
│ │ │ │ └─ MaxServiceRecordCount=16
│ │ │ └─ ServiceSearchPattern(嵌套结构)
│ │ └─ ParameterLength=8
│ └─ TransactionID
└─ PDU ID=0x02
ServiceSearchPattern 嵌套结构:
Sequence (35 03) ─────────────┐
└─ UUID-16 (19) ── 11 0B │ → 0x110B
──────────────────────────────┘
③ SDP_ServiceSearchResponse (0x03)
返回匹配的Service Record Handle列表。
Parameters:
+-------------------------+---------------------------+-------------------------+-------------------+
| TotalServiceRecordCount | CurrentServiceRecordCount | ServiceRecordHandleList | ContinuationState |
| (2字节) | (2字节) | (可变) | (可变) |
+-------------------------+---------------------------+-------------------------+-------------------+
| 字段 | 说明 |
|---|---|
| TotalServiceRecordCount | 匹配的总记录数 |
| CurrentServiceRecordCount | 本次返回的记录数 |
| ServiceRecordHandleList | Handle列表(每个4字节),方便后续进行对应的连接,一次可以进行多个句柄返回 |
| ContinuationState | 分片继续状态 |
④ SDP_ServiceAttributeRequest (0x04)
根据Service Record Handle查询指定属性。
Parameters:
+---------------------+-----------------------+-----------------+-------------------+
| ServiceRecordHandle | MaxAttributeByteCount | AttributeIDList | ContinuationState |
| (4字节) | (2字节) | (Data Element) | (可变) |
+---------------------+-----------------------+-----------------+-------------------+
| 字段 | 说明 |
|---|---|
| ServiceRecordHandle | 要查询的服务记录句柄 |
| MaxAttributeByteCount | 最大返回字节数,超过进行分段 |
| AttributeIDList | 要查询的属性ID列表或范围 |
| ContinuationState | 分片继续状态 |
AttributeIDList 格式详解:
AttributeIDList 是一个 Data Element Sequence,里面可以包含两种元素:
| 类型 | 字节数 | Header | 说明 |
|---|---|---|---|
| 单个 Attribute ID | 2字节 | 09 |
查询指定的单个属性 |
| Attribute ID Range | 4字节 | 0A |
查询一个范围内的所有属性 |
区分依据 :通过 Data Element 的 Size Index 判断
09= Type 1 (Unsigned Int) + Size Index 1 (2字节) → 单个ID0A= Type 1 (Unsigned Int) + Size Index 2 (4字节) → Range
Attribute ID Range 格式:
+------------------+------------------+
| 起始ID (高16位) | 结束ID (低16位) |
+------------------+------------------+
示例:
查询单个属性 0x0001(ServiceClassIDList):
35 03 09 00 01
↑ ↑ ↑────↑
│ │ Attribute ID = 0x0001
│ └─ Unsigned Int, 2字节
└─ Sequence, 长度3
查询属性范围 0x0000 ~ 0xFFFF(所有属性):
35 05 0A 00 00 FF FF
↑ ↑ ↑─────────↑
│ │ Range: 起始0x0000, 结束0xFFFF
│ └─ Unsigned Int, 4字节
└─ Sequence, 长度5
混合查询(单个ID + 范围):
35 08 09 00 01 0A 00 04 01 00
↑ ↑──────↑ ↑───────────↑
│ │ └─ Range: 0x0004 ~ 0x0100
│ └─ 单个ID: 0x0001
└─ Sequence, 长度8
含义:查询 0x0001 + (0x0004到0x0100范围内的所有属性)
⑤ SDP_ServiceAttributeResponse (0x05)
返回请求的属性值。
Parameters:
+------------------------+----------------+-------------------+
| AttributeListByteCount | AttributeList | ContinuationState |
| (2字节) | (Data Element) | (可变) |
+------------------------+----------------+-------------------+
注意:AttributeListByteCount 是AttributeList 的字节长度
AttributeList 返回格式:
无论请求的是单个ID 还是ID范围,返回格式都相同:
Sequence [
Attribute ID 1, Attribute Value 1,
Attribute ID 2, Attribute Value 2,
...
]
注意 :只返回服务记录中实际存在的属性,不存在的不会返回。
示例:假设服务记录只有 0x0000、0x0001、0x0004 三个属性
| 请求 | 返回 |
|---|---|
单个ID 0x0001 |
只返回 [0x0001, Value] |
范围 0x0000~0xFFFF |
返回 [0x0000, Value], [0x0001, Value], [0x0004, Value] |
单个ID 0x0099 |
返回空(该属性不存在) |
返回示例(查询 0x0000~0xFFFF):
05 00 09 00 1E 00 1B 35 19 ... 00
↑ ↑────↑ ↑────↑ ↑────↑ ↑───↑ ↑
│ │ │ │ │ └─ ContinuationState=0(无后续)
│ │ │ │ └─ AttributeList (Sequence)
│ │ │ └─ AttributeListByteCount
│ │ └─ Parameter Length
│ └─ Transaction ID
└─ PDU ID = 0x05
AttributeList 内容:
35 19 ← Sequence, 长度25字节
├── 09 00 00 ← Attr ID = 0x0000
│ 0A 00 01 00 05 ← Value: 0x00010005 (ServiceRecordHandle)
├── 09 00 01 ← Attr ID = 0x0001
│ 35 03 19 11 0B ← Value: [UUID 0x110B] (ServiceClassIDList)
└── 09 00 04 ← Attr ID = 0x0004
35 08 35 06 19 01 00 09 00 19 ← Value: [[L2CAP, PSM=0x19]] (ProtocolDescriptorList)
注意:数据是采用的嵌套数据格式,全部按照 Data Element 格式,同时每个元素都有自己的 Header,告诉你类型和长度。
如果数据太大:超过 MaxAttributeByteCount 时,通过 ContinuationState 分多次返回,每次返回部分属性。
⑥ SDP_ServiceSearchAttributeRequest (0x06) 最常用
最常用的请求,组合了服务搜索和属性查询,一次完成。
Parameters:
+----------------------+-----------------------+------------------+-------------------+
| ServiceSearchPattern | MaxAttributeByteCount | AttributeIDList | ContinuationState |
| (Data Element) | (2字节) | (Data Element) | (可变) |
+----------------------+-----------------------+------------------+-------------------+
| 字段 | 说明 |
|---|---|
| ServiceSearchPattern | 要搜索的UUID列表 |
| MaxAttributeByteCount | 最大返回字节数 |
| AttributeIDList | 要查询的属性ID列表 |
| ContinuationState | 分片继续状态 |
⑦ SDP_ServiceSearchAttributeResponse (0x07)
返回匹配服务的属性列表。
Parameters:
+-------------------------+----------------+-------------------+
| AttributeListsByteCount | AttributeLists | ContinuationState |
| (2字节) | (Data Element) | (可变) |
+-------------------------+----------------+-------------------+
Continuation State(分片机制)
当返回数据太大时,SDP使用Continuation State进行分片传输,需要再次Request同时附上上次的ContinuationState:
Client Server
| |
|--- Request (ContState=0x00) ---------->| 首次请求
|<-- Response (ContState=0x05 XXXXX) ----| 数据未完,返回继续状态
| |
|--- Request (ContState=0x05 XXXXX) ---->| 带上继续状态
|<-- Response (ContState=0x00) ----------| 数据完毕,继续状态为空
- ContinuationState格式:1字节长度 + N字节状态数据
- 首次请求:ContinuationState = 0x00(1字节,表示长度为0)
- 继续请求:把上次响应的ContinuationState原样带回
典型查询流程
使用ServiceSearchAttributeRequest(推荐):
Client Server
| |
| "查找A2DP Sink服务的连接信息" |
| |
|--- ServiceSearchAttributeRequest ----->|
| ServiceSearchPattern: [0x110B] | 搜索AudioSink
| AttributeIDList: [0x0000-0x0100] | 查询基本属性
| |
|<-- ServiceSearchAttributeResponse -----|
| AttributeLists: [ |
| [0x0000]=0x00010001, | Handle
| [0x0001]=[0x110B], | 服务类
| [0x0004]=[[L2CAP,0x19],[AVDTP]] | 协议描述
| ] |
| |
| 得到PSM=0x0019,可以建立连接了 |