Protocol Axis: Vol 3, Part G, 3.1 Service definition (服务定义)
快速要点 (Key Takeaways)
- 服务是 GATT 的最小容器:所有属性必须属于某个服务,没有孤立的属性。
- 界桩式边界:服务定义由"服务声明"开启,遇到下一个声明或达到 0xFFFF 句柄即结束。
- 严格的层级排队:服务内部必须遵循"声明 -> 包含 -> 特征"的顺序,不能错位。
- 零安全壁垒:为了保证发现效率,服务声明必须是只读且无需任何安全验证的。
SIG 规范对照解析
3.1 Service definition (服务定义)
【段落 1:定义与边界】
原文引用:
shell
A service definition shall contain a service declaration and may contain include definitions and characteristic definitions. The service definition ends before the next service declaration or after the maximum Attribute Handle is reached. Service definitions appear on the server in an order based on Attribute Handle.
原文翻译 :
服务定义应 (shall) 包含一个服务声明,并可能 (may) 包含包含定义 (include definitions) 和特征定义 (characteristic definitions)。服务定义在下一个服务声明之前或达到最大属性句柄 (Attribute Handle) 后结束。服务器上的服务定义按照属性句柄的顺序排列。
- 重点词汇 :
- Maximum Attribute Handle: 最大属性句柄 (0xFFFF)。
- In an order based on: 基于...的顺序。
技术释义 :
服务是 GATT 数据库中的"逻辑分区"。协议中没有一个字段显式标注"这个服务包含 X 个属性",而是通过属性句柄 (Attribute Handle) 的连续性来隐含定义的。当客户端通过 Read By Group Type 搜索 UUID 0x2800 时,它会得到一组起始句柄,而每个服务的结束句柄就是下一个服务的起始句柄减 1。
协议本质 (人话版) :
服务就像是书架上的一个个书架隔间。每个隔间开头贴个标签(服务声明)。从这个标签开始,一直到看到下一个标签为止,中间所有的书(包含/特征)都属于这个隔间。
【段落 2:层级顺序要求】
原文引用:
shell
All include definitions and characteristic definitions contained within the service definition are considered to be part of the service. All include definitions shall immediately follow the service declaration and precede any characteristic definitions. A service definition may have zero or more include definitions. All characteristic definitions shall be immediately following the last include definition or, in the event of no include definitions, immediately following the service declaration. A service definition may have zero or more characteristic definitions. There is no upper limit for include or characteristic definitions.
原文翻译 :
服务定义中包含的所有包含定义和特征定义都被视为该服务的一部分。所有包含定义应 (shall) 紧随服务声明之后,并位于任何特征定义之前。一个服务定义可以有零个或多个包含定义。所有特征定义应 (shall) 紧随最后一个包含定义之后;如果没有包含定义,则紧随服务声明之后。服务定义可以有零个或多个特征定义。包含定义或特征定义的数量没有上限。
- 重点短语 :
- Immediately follow: 紧随其后。
- Precede: 在...之前。
技术释义 :
这一段规定了数据库内部的拓扑结构。
- 强制顺序:[Service Decl] -> [Include 1...N] -> [Char 1...N]。
- 设计初衷:这种固定顺序极大地方便了客户端的扫描逻辑。客户端可以在扫描到第一个特征声明时,确定该服务所有的包含定义已经全部读取完毕,无需继续往后搜索包含定义。
协议本质 (人话版) :
服务里的内容排队是有讲究的:"服务声明"是排头兵,"包含定义"是第一梯队,"特征定义"是第二梯队。第一梯队必须全在第二梯队前面,不能插队。
【段落 3:声明属性详情】
原文引用:
shell
A service declaration is an Attribute with the Attribute Type set to the UUID for <<Primary Service>> or <<Secondary Service>>. The Attribute Value shall be the 16-bit Bluetooth UUID or 128-bit UUID for the service, known as the service UUID. A client shall support the use of both 16-bit and 128-bit UUIDs. A client may ignore any service definition with an unknown service UUID. An unknown service UUID is a UUID for an unsupported service. The Attribute Permissions shall be read-only and shall not require authentication or authorization.
原文翻译 :
服务声明是一个属性,其属性类型 (Attribute Type) 设置为"主服务 (Primary Service)"或"从服务 (Secondary Service)"的 UUID。属性值 (Attribute Value) 应为该服务的 16 位蓝牙 UUID 或 128 位 UUID,称为服务 UUID。客户端应 (shall) 支持同时使用 16 位和 128 位 UUID。客户端可以忽略任何具有未知服务 UUID 的服务定义。未知服务 UUID 是指不受支持的服务 UUID。属性权限应 (shall) 为只读,且不应 (shall not) 要求认证或授权。
- 重点词汇 :
- Primary Service: 主服务 (0x2800)。
- Secondary Service: 从服务 (0x2801)。
技术释义:
- 权限全开 :服务声明必须是
Read-Only且No Auth。这样做是为了让任何连接上的设备都能快速构建出一张功能地图。 - UUID 兼容:SIG 定义了 16-bit UUID,但也通过 128-bit UUID 留出了无限的自定义空间。
协议本质 (人话版) :
服务声明就像名片上的姓名。无论你是否信任这个人,你至少应该能看到他的名字。具体的联系方式(特征值)可能需要验证身份,但名字(服务声明)必须公开。
【段落 4:UUID 分组建议】
原文引用:
shell
When multiple services exist, services definitions with service declarations using 16-bit Bluetooth UUID should be grouped together (i.e. listed sequentially) and services definitions with service declarations using 128-bit UUID should be grouped together.
原文翻译 :
当存在多个服务时,使用 16 位蓝牙 UUID 的服务定义应该 (should) 组合在一起(即连续列出),使用 128 位 UUID 的服务定义也应该 (should) 组合在一起。
技术释义 :
这是一种性能优化建议。因为 16-bit UUID 在 ATT 报文中所占空间较小。如果它们连续排列,客户端可以使用 Read By Group Type 在一个数据包内读取更多的服务信息,减少往返次数(RTT)。
【表格 3.1:服务声明属性详情 (Table 3.1)】
| 属性地址 (Handle) | 属性类型 (Type) | 属性值 (Value) | 属性权限 (Permission) |
|---|---|---|---|
| 0xNNNN | 0x2800 (Primary) 或 0x2801 (Secondary) | 16-bit 或 128-bit Service UUID | 只读, 无认证, 无授权 |
【段落 5-7:规则补遗】
原文引用:
shell
A device or higher level specification may have multiple service definitions and may have multiple service definitions with the same service UUID.
All Attributes on a server shall either contain a service declaration or exist within a service definition.
Service definitions contained in a server may appear in any order; a client shall not assume the order of service definitions on a server.
原文翻译 :
设备或高级规范可以具有多个服务定义,并且可以具有多个具有相同服务 UUID 的服务定义。服务器上的所有属性要么包含一个服务声明,要么存在于一个服务定义中。服务器中包含的服务定义可以以任何顺序出现;客户端不应 (shall not) 假设服务器上服务定义的顺序。
技术释义:
- 禁止"单身"属性:数据库里任何一个属性,往回找(句柄递减方向),一定能找到一个服务声明。
- 动态发现:虽然有分组建议,但协议栈实现必须能处理乱序的服务排列,增加鲁棒性。
核心术语对照
| 英文原文 | 中文翻译 | 技术含义 |
|---|---|---|
| Service Declaration | 服务声明 | 标志服务起始的特殊属性 (0x2800/0x2801) |
| Attribute Handle | 属性句柄 | 属性在数据库中的 16 位唯一地址 |
| Primary Service | 主服务 | 具有核心功能的独立入口服务 |
| Secondary Service | 从服务 | 被主服务引用的辅助服务 |
交互流程 (Mermaid)
展示客户端如何发现并识别服务的边界:
Server (GATT DB) Client Server (GATT DB) Client [Hdl: 0x0001] Type: 0x2800, Value: Heart Rate [Hdl: 0x0002] Type: 0x2803, Value: HR Measurement [Hdl: 0x0003] Value: 75bpm [Hdl: 0x0010] Type: 0x2800, Value: Battery 客户端得知: Heart Rate 服务的边界是 0x0001 到 0x000F Read By Group Type (0x2800) Handle Range: 0x0001 - 0x000F, UUID: Heart Rate Handle Range: 0x0010 - 0xFFFF, UUID: Battery
需要注意的点
- 句柄跳跃:虽然属性在逻辑上属于服务,但不同服务之间的句柄不一定是连续的(可以留空隙),但服务内部的属性句柄必须是递增的。
- 安全风险:不要在服务声明属性中存储敏感信息,因为它对所有连接都是公开的。
生活案例
酒店的楼层布局:
- 服务声明:电梯出来的导向牌(如"3 楼:餐饮服务区")。
- 包含定义:导向牌下面的小字,"3 楼包含:后厨管理间(从服务)"。
- 特征定义:3 楼具体的客房或功能间(如"中餐厅"、"西餐厅")。
- 规则:你进门(连接)后,看一眼导向牌(服务声明)是不需要房卡(认证)的。