在蓝牙设备互联互通的过程中,存在一个容易被忽视但至关重要的握手环节------当蓝牙鼠标靠近电脑、无线键盘尝试连接平板时,设备间需要先完成自我介绍:明确彼此支持的服务、通信协议、功能参数,才能建立稳定的连接。这个自我介绍的核心机制,就是蓝牙SDP(Service Discovery Protocol,服务发现协议)。
目录
[2.1 基础标识属性:设备的身份信息](#2.1 基础标识属性:设备的身份信息)
[2.2 语言与字符串属性:设备的人性化说明](#2.2 语言与字符串属性:设备的人性化说明)
[2.3 HID专用属性:设备的功能说明书](#2.3 HID专用属性:设备的功能说明书)
[2.4 示例服务记录:鼠标的完整简历](#2.4 示例服务记录:鼠标的完整简历)
[3.1 流程一:服务搜索(ServiceSearchRequest)------找到合适的设备](#3.1 流程一:服务搜索(ServiceSearchRequest)——找到合适的设备)
[3.2 流程二:属性查询(ServiceAttributeRequest)------了解设备的具体能力](#3.2 流程二:属性查询(ServiceAttributeRequest)——了解设备的具体能力)
[3.3 流程三:服务搜索与属性查询结合(ServiceSearchAttributeRequest)------高效获取完整信息](#3.3 流程三:服务搜索与属性查询结合(ServiceSearchAttributeRequest)——高效获取完整信息)
[3.4 SDP数据编码规则:解析数据的密码本](#3.4 SDP数据编码规则:解析数据的密码本)
蓝牙HID规范中通过大量实例,详细定义了SDP的交互流程、数据格式和参数含义,涵盖服务搜索、属性查询、多属性批量获取等典型场景。本文以规范中的鼠标SDP服务记录为基础,深入拆解SDP的核心逻辑、交互流程和数据解析方法,结合实战案例让开发者吃透SDP的工作机制,同时为面试和实际开发提供全面参考。
一、SDP的核心定位
SDP的本质是一套标准化的服务发现与信息查询协议,相当于蓝牙设备间的服务黄页------设备将自身支持的服务(如HID输入服务、音频服务)、通信参数(如L2CAP通道、PSM值)、功能特性(如是否支持Boot协议、是否电池供电)等信息,以服务记录(Service Record)的形式存储在SDP服务器中;需要连接的设备(SDP客户端)通过发送查询请求,获取目标设备的服务信息,进而建立针对性的通信连接。
对于蓝牙HID设备而言,SDP的核心作用有三个:
服务匹配:客户端确认目标设备是否支持HID服务,避免连接不兼容的设备;
参数协商:获取HID服务的关键参数(如控制通道PSM值、报告描述符、协议支持情况),为后续L2CAP通道建立、数据传输提供依据;
功能识别:明确设备类型(如键盘、鼠标、手柄)、支持的特性(如远程唤醒、虚拟线缆),让客户端提前适配设备功能。
可以把SDP交互想象成一场设备相亲:SDP服务器是携带个人简历(服务记录)的候选人,SDP客户端是筛选候选人的面试官,通过一系列提问(查询请求),面试官(客户端)了解候选人(服务器)的技能(服务)和条件(参数),确认匹配后再进入深入合作(建立通信连接)阶段。
规范中明确,HID设备的SDP服务记录必须包含一系列 mandatory(强制)属性和可选属性,这些属性共同构成了设备的完整简历,确保客户端能全面了解设备能力。
二、HID设备SDP服务记录详解:一份完整的设备简历
规范中以蓝牙鼠标为例,提供了一份完整的SDP服务记录实例,包含30+核心属性,涵盖服务标识、协议描述、语言支持、功能特性等多个维度。我们逐一解析这些属性的含义和作用,理解SDP服务记录的构成逻辑。
2.1 基础标识属性:设备的身份信息
这类属性用于唯一标识设备的服务类型和身份,是客户端筛选设备的第一依据:
-
ServiceRecordHandle(服务记录句柄):唯一标识一条服务记录的32位数值(如0x0A00010002),客户端后续查询属性时需指定该句柄;
-
ServiceClassIDList(服务类ID列表):指定设备支持的服务类型,HID设备需包含HID服务类ID(0x1124),相当于设备的职业标签,告诉客户端"我是HID设备";
-
ProtocolDescriptorList(协议描述列表):定义服务使用的通信协议栈,HID设备需包含L2CAP和HID协议:
-
L2CAP协议:UUID为0x0100,参数为HID控制通道PSM值(0x0011);
-
HID协议:UUID为0x0011,无额外参数;
-
-
BluetoothProfileDescriptorList(蓝牙配置文件描述列表):指定设备遵循的蓝牙配置文件,HID设备需包含HID配置文件ID(0x1124)和版本号(如0x0101,对应版本1.1)。
这些基础属性构成了设备的身份名片,客户端通过ServiceClassIDList快速筛选出HID设备,再通过ProtocolDescriptorList了解通信方式,为后续连接做准备。
2.2 语言与字符串属性:设备的人性化说明
这类属性用于提供设备的可读信息,方便用户识别和使用:
-
LanguageBaseAttributeIDList(语言基础属性ID列表):定义设备支持的语言、字符编码和字符串属性基准ID,例如:
-
语言:英语(0x656E);
-
字符编码:UTF-8(0x006A);
-
基准ID:0x0100(表示字符串属性的ID从0x0100开始);
-
-
ServiceName(服务名称):设备的可读名称(如"XYZ Mouse"),显示在客户端设备列表中;
-
ServiceDescription(服务描述):设备功能说明(如"Three button Mouse");
-
ProviderName(提供商名称):设备制造商名称(如"XYZ Company")。
规范中强调,字符串属性需支持至少一种语言,确保用户能直观识别设备,这也是提升用户体验的重要细节。
2.3 HID专用属性:设备的功能说明书
这类属性是HID设备特有的,详细描述设备的功能特性、协议支持、数据格式等关键信息,是客户端适配设备的核心依据:
HIDParserVersion(HID解析器版本):设备遵循的USB HID规范版本,以16位数值表示(如0x0111对应版本1.11),确保客户端解析报告描述符时兼容;
HIDDeviceSubclass(HID设备子类):8位数值,标识HID设备的具体类型,例如鼠标支持Boot协议时该值为0x80;
HIDCountryCode(HID国家代码):标识设备的硬件本地化地区(如0x21对应美国),主要用于键盘等设备的键位布局适配;
HIDVirtualCable(虚拟线缆支持):布尔值(TRUE/FALSE),标识设备是否支持虚拟线缆功能;
HIDReconnectInitiate(重连发起支持):布尔值,标识设备是否能主动发起重连;
HIDDescriptorList(HID描述符列表):包含设备的报告描述符(Report Descriptor)和物理描述符(Physical Descriptor),其中报告描述符是核心,定义了设备的数据报告格式(如鼠标的位置数据、按键数据格式);
HIDLANGIDBaseList(HID语言ID基础列表):映射HID语言ID与蓝牙语言属性,确保多语言字符串的正确解析;
HIDBatteryPower(电池供电标识):布尔值,标识设备是否为电池供电,客户端可据此调整功耗管理策略;
HIDRemoteWake(远程唤醒支持):布尔值,标识设备是否能唤醒主机;
HIDBootDevice(Boot设备支持):布尔值,标识设备是否支持Boot协议模式。
这些属性构成了HID设备的功能说明书,客户端通过这些属性了解设备的具体能力,例如是否支持Boot协议、是否需要低功耗管理、数据报告格式如何解析等,为后续数据传输和功能适配提供关键依据。
2.4 示例服务记录:鼠标的完整简历


规范中提供的鼠标SDP服务记录包含30+属性,以下是核心属性的完整示例(简化版):
|----------|--------------------------------|----------------------------------------|------------------|
| 属性ID | 属性名称 | 属性值 | 说明 |
| 0x0000 | ServiceRecordHandle | 0x0A00010002 | 服务记录唯一标识 |
| 0x0001 | ServiceClassIDList | 0x190x1124 | 支持HID服务 |
| 0x0004 | ProtocolDescriptorList | 包含L2CAP(0x0100,PSM=0x0011)和HID(0x0011) | 通信协议栈 |
| 0x0006 | LanguageBaseAttributeIDList | 英语(0x656E)、UTF-8(0x006A)、基准ID(0x0100) | 语言与编码配置 |
| 0x0009 | BluetoothProfileDescriptorList | HID配置文件(0x1124)、版本1.1(0x0101) | 遵循的蓝牙配置文件 |
| 0x0201 | HIDParserVersion | 0x0111 | 遵循USB HID 1.11规范 |
| 0x0202 | HIDDeviceSubclass | 0x80 | 支持Boot协议的鼠标 |
| 0x0203 | HIDCountryCode | 0x21 | 美国地区 |
| 0x0204 | HIDVirtualCable | TRUE | 支持虚拟线缆 |
| 0x0205 | HIDReconnectInitiate | TRUE | 支持主动重连 |
| 0x0206 | HIDDescriptorList | 包含3键2轴鼠标的报告描述符 | 数据报告格式定义 |
| 0x0207 | HIDLANGIDBaseList | 英语(0x0409)、基准ID(0x0100) | HID语言与蓝牙语言映射 |
| 0x0209 | HIDBatteryPower | TRUE | 电池供电 |
| 0x020A | HIDRemoteWake | TRUE | 支持远程唤醒 |
| 0x020E | HIDBootDevice | TRUE | 支持Boot协议 |
这份服务记录完整描述了鼠标的身份、通信方式、功能特性和数据格式,客户端获取后即可全面了解设备能力,进而建立针对性的连接。
三、SDP交互的三大核心流程:从搜索到解析的全步骤
规范中详细定义了SDP的三类典型交互流程:服务搜索(ServiceSearchRequest)、属性查询(ServiceAttributeRequest)、服务搜索与属性查询结合(ServiceSearchAttributeRequest)。这三类流程覆盖了从找到设备到了解设备的完整过程,我们结合规范中的示例逐一拆解。
3.1 流程一:服务搜索(ServiceSearchRequest)------找到合适的设备
服务搜索是客户端的第一步操作,目的是从周边蓝牙设备中筛选出支持目标服务(如HID服务)的设备,获取其服务记录句柄。
(1)交互逻辑
客户端发送服务搜索请求,指定服务搜索模式(如LIAC,有限查询访问码)、服务搜索模式(如HID服务类ID)、最大返回服务记录数;服务器响应搜索结果,返回符合条件的服务记录句柄列表。
(2)请求与响应数据格式(简化版)
-
客户端请求(ServiceSearchRequest):
PDUID: 0x02(服务搜索请求) TransactionID: 0xFFFF(事务ID,用于匹配请求与响应) ParameterLength: 0x0008(参数长度) ServiceSearchPattern: 0x190x1124(搜索HID服务类ID) MaximumServiceRecordCount: 0x0003(最多返回3条记录) ContinuationState: 0x00(无续传状态) -
服务器响应(ServiceSearchResponse):
PDUID: 0x03(服务搜索响应) TransactionID: 0xFFFF(与请求一致) ParameterLength: 0x0009(参数长度) TotalServiceRecordCount: 0x0001(符合条件的记录总数) CurrentServiceRecordCount: 0x0001(当前返回的记录数) ServiceRecordHandleList: 0x0A00010002(服务记录句柄) ContinuationState: 0x00(无续传)
(3)关键说明
-
服务搜索模式(ServiceSearchPattern)支持单个或多个服务类ID,客户端可通过该参数筛选特定类型的设备;
-
事务ID(TransactionID)用于匹配请求与响应,避免多个并发请求时混淆;
-
续传状态(ContinuationState)用于处理搜索结果过多的情况,服务器返回部分结果时会设置该字段,客户端需携带该字段继续请求剩余结果。
3.2 流程二:属性查询(ServiceAttributeRequest)------了解设备的具体能力
获取服务记录句柄后,客户端通过属性查询请求,获取该服务记录的指定属性,深入了解设备的功能特性。
(1)交互逻辑
客户端发送属性查询请求,指定服务记录句柄、最大返回字节数、需要查询的属性ID列表;服务器响应查询结果,返回指定属性的名称和值。
(2)请求与响应数据格式(简化版)
-
客户端请求(ServiceAttributeRequest):
PDUID: 0x04(属性查询请求) TransactionID: 0xEEEE(事务ID) ParameterLength: 0x000C(参数长度) ServiceRecordHandle: 0x0A00010002(服务记录句柄) MaximumAttributeByteCount: 0x0080(最大返回字节数) AttributeIDList: 0x0004(查询协议描述列表属性) ContinuationState: 0x00(无续传) -
服务器响应(ServiceAttributeResponse):
PDUID: 0x05(属性查询响应) TransactionID: 0xEEEE(与请求一致) ParameterLength: 0x0017(参数长度) AttributeListByteCount: 0x0014(属性列表字节数) AttributeList: [ AttributeID: 0x0004(协议描述列表), AttributeValue: 包含L2CAP和HID协议的描述序列 ] ContinuationState: 0x00(无续传)
(3)关键说明
-
属性ID列表(AttributeIDList)支持单个或多个属性ID,客户端可按需查询,避免获取冗余数据;
-
最大返回字节数(MaximumAttributeByteCount)用于限制服务器返回的数据量,防止数据包过大导致传输失败;
-
对于复杂属性(如协议描述列表、HID描述符列表),属性值以数据元素序列(Data Element Sequence)的形式返回,客户端需按SDP数据编码规则解析。
3.3 流程三:服务搜索与属性查询结合(ServiceSearchAttributeRequest)------高效获取完整信息
如果客户端需要同时搜索设备并获取其属性,可使用ServiceSearchAttributeRequest,将服务搜索和属性查询合并为一次交互,提升效率。
(1)交互逻辑
客户端发送合并请求,指定服务搜索模式、最大返回字节数、需要查询的属性ID列表;服务器响应搜索结果,返回符合条件的服务记录及其指定属性。
(2)请求与响应数据格式(简化版)
-
客户端请求(ServiceSearchAttributeRequest):
PDUID: 0x06(合并请求) TransactionID: 0xDDDD(事务ID) ParameterLength: 0x001C(参数长度) ServiceSearchPattern: 0x190x1124(搜索HID服务) MaximumAttributeByteCount: 0x0190(最大返回字节数) AttributeIDList: 0x0000-0x0001, 0x0006, 0x0100-0x0102, 0x0200-0x020C(属性ID范围) ContinuationState: 0x00(无续传) -
服务器响应(ServiceSearchAttributeResponse):
PDUID: 0x07(合并响应) TransactionID: 0xDDDD(与请求一致) ParameterLength: 0x00DB(参数长度) AttributeListByteCount: 0x00D8(属性列表字节数) AttributeLists: [ 服务记录句柄0x0A00010002的属性列表,包含指定的所有属性 ] ContinuationState: 0x00(无续传)
(3)关键说明
-
该流程适用于需要批量获取设备信息的场景,避免多次交互导致的延迟;
-
属性ID列表支持连续的属性ID范围(如0x0000-0x0001),简化请求参数;
-
若搜索结果包含多个服务记录,服务器会返回多个属性列表,每个属性列表对应一条服务记录。
3.4 SDP数据编码规则:解析数据的密码本
SDP交互中,属性值采用特定的编码规则(BER编码变体),客户端需按该规则解析才能正确获取数据。核心编码规则如下:
-
每个数据元素由数据元素头部和数据元素值组成;
-
数据元素头部(1字节)包含类型描述符(高3位)和长度描述符(低5位):
-
类型描述符:指定数据类型(如UUID、整数、字符串、序列);
-
长度描述符:指定数据元素值的长度(0-31字节),超过31字节时需使用扩展长度字段;
-
-
常见数据类型编码:
-
UUID16:类型描述符0x03(001),长度2字节;
-
无符号8位整数:类型描述符0x08(0001000),长度1字节;
-
字符串:类型描述符0x25(0100101),长度为字符串字节数;
-
数据元素序列:类型描述符0x35(0110101),长度为序列总字节数。
-
例如,HID服务类ID(0x1124)的编码为:0x19(类型描述符0x03 + 长度2)+ 0x11 + 0x24,客户端解析时需先识别类型和长度,再提取数据值。
四、多语言字符串属性的特殊处理:支持全球用户

规范中特别定义了多语言字符串属性的处理方式,确保设备在不同地区、不同语言的主机上都能正确显示可读信息。核心机制如下:
1. 多语言支持的实现方式
设备通过HIDLANGIDBaseList属性,定义多种语言的HID语言ID与蓝牙语言属性基准ID的映射关系。例如,支持英语、德语、意大利语的设备,会在该属性中包含三组映射:
英语(HID语言ID 0x0409)→ 蓝牙基准ID 0x0100;
德语(HID语言ID 0x0407)→ 蓝牙基准ID 0x0500;
意大利语(HID语言ID 0x0410)→ 蓝牙基准ID 0x0400。
2. 字符串属性的存储与查询
每种语言的字符串属性(如ServiceName、ServiceDescription)都存储在对应基准ID的偏移位置:
英语的ServiceName存储在0x0100 + 0x0000 = 0x0100;
德语的ServiceName存储在0x0500 + 0x0000 = 0x0500;
意大利语的ServiceName存储在0x0400 + 0x0000 = 0x0400。
客户端查询时,需先通过LanguageBaseAttributeIDList获取目标语言的基准ID,再计算字符串属性的实际ID,进而获取对应语言的字符串。
3. 示例:ATM机的多语言支持
规范中提供了ATM机的多语言SDP服务记录示例,该设备支持英语、德语、意大利语的字符串显示:
英语:ServiceName为"XYZ Automatic Transaction Machine",存储在0x0100;
德语:ServiceName为"XYZ Bankomat",存储在0x0500;
意大利语:ServiceName为"XYZ Macchina Automatica Di Transazione",存储在0x0400。
这种设计确保了设备在不同语言环境下都能为用户提供直观的识别信息,提升全球兼容性。
五、SDP交互的实战优化技巧
基于规范中的交互流程和数据格式,结合实际开发经验,总结以下SDP交互的优化技巧,提升交互效率和稳定性:
1. 按需查询,减少数据传输
客户端应根据实际需求选择查询的属性,避免获取冗余数据。例如,仅需确认设备是否为HID设备时,只需查询ServiceClassIDList;需要建立连接时,再查询ProtocolDescriptorList和HIDDescriptorList。
2. 合理设置参数,避免传输失败
最大返回字节数(MaximumAttributeByteCount)应根据属性大小设置,对于报告描述符等大数据属性,需设置足够大的数值(如0x0190);
服务搜索时的最大返回记录数(MaximumServiceRecordCount)应根据实际场景设置,避免返回过多记录导致数据包过大。
3. 处理续传,确保数据完整
当服务器返回的结果超过单个数据包承载能力时,会设置ContinuationState字段,客户端需携带该字段继续发送请求,直至获取完整数据。
4. 缓存服务记录,减少重复查询
客户端可缓存已查询过的设备服务记录,短期内再次连接时无需重新查询,提升连接速度。缓存时需注意服务记录的时效性,设备功能变更后需重新查询。
5. 兼容旧版本设备
部分旧版本HID设备的SDP服务记录可能缺少部分可选属性,客户端需具备容错能力,对于缺失的可选属性,使用默认值或忽略。
六、常见问题与避坑指南
1. 服务搜索不到目标设备
可能原因:
-
目标设备未进入可发现模式;
-
客户端的服务搜索模式(ServiceSearchPattern)设置错误,未包含HID服务类ID;
-
设备的SDP服务记录未正确配置ServiceClassIDList。
解决方法:
-
确保目标设备处于可发现模式;
-
检查服务搜索模式参数,确认包含0x1124(HID服务类ID);
-
验证目标设备的SDP服务记录配置。
2. 属性解析失败
可能原因:
-
客户端未按SDP编码规则解析数据元素;
-
服务器返回的属性值格式错误;
-
客户端查询的属性ID不存在或为可选属性(设备未配置)。
解决方法:
-
严格按照SDP编码规则解析数据,先解析头部获取类型和长度,再提取数据值;
-
对可选属性设置默认值,避免解析失败导致程序崩溃;
-
验证属性ID的正确性,参考规范中的HID设备必填属性列表。
3. 交互延迟过高
可能原因:
-
客户端多次发送单独的服务搜索和属性查询请求;
-
未处理续传,导致多次交互;
-
蓝牙信号干扰,导致数据包重传。
解决方法:
-
使用ServiceSearchAttributeRequest合并请求,减少交互次数;
-
正确处理续传,一次性获取完整数据;
-
优化蓝牙链路参数,减少信号干扰。
七、检验
问题:蓝牙HID设备的SDP协议主要作用是什么?客户端如何通过SDP找到并适配HID设备?
答案:
SDP协议的核心作用是服务发现与信息交互,为蓝牙HID设备与主机(客户端)提供自我介绍的标准化方式,让客户端了解设备的服务类型、通信协议、功能特性,为后续连接和数据传输奠定基础。
客户端适配HID设备的流程分为三步:
-
服务搜索:客户端发送ServiceSearchRequest,指定HID服务类ID(0x1124),筛选出支持HID服务的设备,获取服务记录句柄;
-
属性查询:发送ServiceAttributeRequest或ServiceSearchAttributeRequest,获取设备的协议描述列表(通信参数)、HID描述符列表(数据格式)、功能特性(如是否支持Boot协议)等关键属性;
-
连接适配:根据获取的属性配置L2CAP通道(使用PSM=0x0011建立控制通道),解析报告描述符适配数据格式,最终建立稳定的HID连接。
这一流程确保了客户端能快速找到兼容的HID设备,并精准适配其功能。
问题:蓝牙HID设备的SDP服务记录包含哪些核心属性?请举例说明各属性的作用。
答案:
HID设备的SDP服务记录包含基础标识、语言字符串、HID专用三类核心属性,示例及作用如下:
1. 基础标识属性:
-
ServiceClassIDList:包含HID服务类ID(0x1124),标识设备为HID类型;
-
ProtocolDescriptorList:定义L2CAP和HID协议,提供通信参数(如控制通道PSM=0x0011);
2. 语言字符串属性:
-
ServiceName:设备可读名称(如"XYZ Mouse"),方便用户识别;
-
LanguageBaseAttributeIDList:定义语言、编码(如UTF-8)和字符串基准ID,支持多语言显示;
3. HID专用属性:
-
HIDDescriptorList:包含报告描述符,定义数据报告格式(如鼠标的位置、按键数据);
-
HIDBootDevice:布尔值,标识设备是否支持Boot协议,供客户端选择协议模式;
-
HIDVirtualCable:布尔值,标识设备是否支持虚拟线缆,决定连接管理策略。
这些属性共同构成了设备的完整简历,确保客户端全面了解设备能力。
问题:SDP的ServiceSearchRequest、ServiceAttributeRequest、ServiceSearchAttributeRequest三种请求的区别是什么?分别适用于什么场景?
答案:
三种请求的核心区别在于功能范围和交互效率,适用场景如下:
-
ServiceSearchRequest:仅实现服务搜索,返回符合条件的设备服务记录句柄,适用于"仅筛选设备,暂不获取详细信息"的场景(如客户端显示周边HID设备列表);
-
ServiceAttributeRequest:仅实现属性查询,需指定服务记录句柄,返回指定属性,适用于"已找到设备,需获取特定详细信息"的场景(如确认设备是否支持远程唤醒);
-
ServiceSearchAttributeRequest:合并服务搜索和属性查询,一次交互返回设备列表及对应属性,适用于"需要快速获取设备完整信息"的场景(如游戏主机快速适配蓝牙手柄,需同时确认服务类型和通信参数)。
三种请求的设计体现了SDP按需交互的核心思路,平衡了交互效率和数据冗余。