3、SD报文结构解析:Entry、Option、Subscriber/Offer/StopOffer等关键字段

SOME/IP服务发现(SD)报文,说白了就是车载ECU之间互相喊话的“小纸条”。你想想看,一个域控制器要找到另一个域控制器提供的服务,总得有个约定好的格式吧?这个格式就是SD报文。今天我就带你把这张“小纸条”拆开看看,里面到底写了什么。

3.1 SD报文整体骨架

先看一个SD报文的完整结构。我个人习惯把它分成三块:头部、Entry数组、Option数组。

字段 长度(字节) 说明
Message ID 4 固定为0xFFFF8100(SD专用)
Length 4 从Entry数组开始到报文末尾的总长度
Request ID 4 Client ID + Session ID,用于匹配请求/响应
Protocol Version 1 当前固定为0x01
Interface Version 1 SD协议自身版本,通常也是0x01
Message Type 1 0x02 = Notification(SD报文专用)
Return Code 1 0x00(SD报文不使用错误码)
Reserved 4 全0填充
Entry Array 可变 核心内容,每条Entry 16字节
Option Array 可变 附加信息,每条Option长度可变

嗯,这里要注意:Entry和Option是分开存放的。Entry先写,Option后写,中间没有分隔符。解析的时候全靠Length字段来定位。

3.2 Entry:服务发现的“主语”

Entry是SD报文的灵魂。它告诉别人“我在找什么服务”或者“我提供了什么服务”。每条Entry固定16字节,结构如下:

偏移(字节) 字段 长度(位) 说明
0 Type 8 Entry类型:0x00=Find, 0x01=Offer, 0x06=StopOffer, 0x04=Subscribe, 0x05=SubscribeAck, 0x07=SubscribeNack
1 Index 1st Option 8 第一个Option在Option数组中的索引
2 Index 2nd Option 8 第二个Option的索引(没有则填0xFF)
3 Number of Option 1 4 第一个Option组包含的Option数量
3 Number of Option 2 4 第二个Option组包含的Option数量
4-7 Service ID 32 服务ID(16位)+ Instance ID(16位)
8-11 Major Version 8 主版本号
8-11 TTL 24 生存时间(秒),0表示立即失效
12-15 Minor Version 32 次版本号(仅用于Find/Offer/Subscribe)

关键点:Entry的Type字段决定了整个报文的语义。同一个报文中可以混合多种Entry类型,但解析时必须按顺序处理。

3.3 三种核心Entry类型

3.3.1 Offer Service Entry

当ECU启动并准备好提供服务时,它会发送Offer报文。Type=0x01。我记得第一次调试时,怎么都收不到对端的Offer,后来发现是TTL设成了0——嗯,这等于告诉别人“我马上就不干了”。

  • Service ID + Instance ID:唯一标识一个服务实例
  • TTL:通常设为3秒(后续靠周期性重发维持)
  • Major/Minor Version:客户端必须匹配Major版本才能订阅

3.3.2 Subscribe Eventgroup Entry

客户端想接收某个服务的事件通知时,发送Subscribe报文。Type=0x04。这里有个坑:Subscribe Entry不包含IP和端口信息,这些信息放在Option里。

我曾经踩过的坑:Subscribe报文中的Reserved字段必须填0。有一次我为了“优化”填了个非零值,结果对端直接丢弃了报文。调试了整整一个下午才发现。

3.3.3 Stop Offer Entry

服务端要下线时,发送StopOffer。Type=0x06。它的TTL固定为0,表示立即失效。我个人习惯在StopOffer中只带一个IPv4 Endpoint Option,告诉客户端“别再来找我了”。

3.4 Option:服务发现的“状语”

Option用来描述Entry的细节——比如IP地址、端口、通信协议等。没有Option,Entry就是个空壳子。

Option Type Type值 长度(字节) 用途
IPv4 Endpoint 0x04 12 提供IPv4地址 + UDP/TCP端口
IPv6 Endpoint 0x06 24 提供IPv6地址 + UDP/TCP端口
Multicast 0x14 12 组播地址 + 端口(用于事件通知)
Configuration 0x01 可变 键值对形式的配置参数
Load Balancing 0x09 8 优先级 + 权重(用于冗余服务)

你想想看,一个Offer Entry配上IPv4 Endpoint Option,就完整表达了:“我在192.168.1.100:30509提供Service ID=0x1234的服务”。

3.5 Entry与Option的关联机制

这是初学者最容易搞混的地方。Entry中的Index 1st Option和Index 2nd Option指向的是Option数组中的起始位置,而Number of Option 1/2告诉你有几个连续的Option属于这个Entry。

我的调试技巧:抓包时先数Option数组的总长度,再根据每个Entry的Index字段反推。如果发现Index超出了Option数组范围,那一定是发送端组包逻辑出了问题。

3.6 完整报文示例

下面是一个Offer报文的实际例子(十六进制):

// 头部
FF FF 81 00  // Message ID = 0xFFFF8100
00 00 00 28  // Length = 40字节
00 00 00 01  // Request ID
01           // Protocol Version = 1
01           // Interface Version = 1
02           // Message Type = Notification
00           // Return Code = 0
00 00 00 00  // Reserved

// Entry Array(1条Offer Entry)
01           // Type = Offer
00           // Index 1st Option = 0
FF           // Index 2nd Option = 0xFF(无)
10           // Num Opt 1 = 1, Num Opt 2 = 0
00 01        // Service ID = 0x0001
00 01        // Instance ID = 0x0001
01           // Major Version = 1
00 00 0E 10  // TTL = 3600秒(1小时)
00 00 00 01  // Minor Version = 1

// Option Array(1条IPv4 Endpoint Option)
00 04        // Type = IPv4 Endpoint
00 08        // Length = 8字节(不含头部)
00 00        // Reserved
C0 A8 01 64  // IPv4 = 192.168.1.100
77 1D        // Port = 30493

3.7 知识结构图

下面这张图帮你理清SD报文的整体脉络:

SOME/IP SD报文结构图 SOME/IP SD报文结构 SD报文头部(16字节) Entry Array(N × 16字节) Option Array(M × 可变长度) Offer (0x01) Subscribe (0x04) StopOffer (0x06) Find (0x00) IPv4 Endpoint Multicast Configuration Load Balancing Entry通过Index字段指向Option数组中的具体位置 每个Entry最多关联2组Option,每组最多包含多个连续Option

3.8 避坑指南

最后,分享几个我实际项目中遇到的坑:

  1. Option索引越界:Entry中的Index字段指向Option数组,但Option数组可能为空。如果Index不为0xFF而Option数组长度为0,报文就是非法的。
  2. TTL与重发周期不匹配:TTL设了3秒,但重发周期设了5秒——这会导致服务在客户端眼中反复“消失又出现”。
  3. Subscribe缺少Endpoint Option:客户端只发了Subscribe Entry,没带Endpoint Option。服务端不知道往哪里发事件通知,订阅等于白费。
  4. StopOffer带了非零TTL:StopOffer的TTL必须为0。我见过有人复制Offer报文改Type忘了改TTL,结果对端以为服务还在。

一句话总结:SD报文 = Entry(做什么)+ Option(怎么做)。解析时先读Entry确定语义,再根据Index找到对应的Option获取细节。顺序不能乱,索引不能错。