4、服务发现Entry格式:Service Entry、Eventgroup Entry的字段解析,Entry Array的排列规则
好,咱们今天聊聊SOME/IP服务发现里最核心的数据结构——Entry。说白了,服务发现就是靠这些Entry来“打招呼”和“报家门”的。你想想看,一辆车上几十个ECU,谁提供什么服务,谁订阅什么事件,总得有个标准格式吧?Entry就是干这个的。
我个人习惯把Entry分成两大类:Service Entry 和 Eventgroup Entry。前者是服务提供者用的,后者是服务消费者用的。咱们一个一个拆开看。
4.1 Service Entry 字段解析
Service Entry,顾名思义,就是用来声明“我这里有这个服务”或者“我想找这个服务”的。它出现在Offer Service、Find Service这些消息里。我刚开始接触SOME/IP时,总觉得这玩意儿字段太多,后来发现其实就几个关键点。
先看它的结构,我用表格列出来:
| 字段 | 长度 | 说明 |
|---|---|---|
| Type | 8 bits | Entry类型,0x00=Find, 0x01=Offer, 0x02=StopOffer |
| Index 1st Option Run | 8 bits | 第一个Option Run的起始索引 |
| Index 2nd Option Run | 8 bits | 第二个Option Run的起始索引 |
| Number of Option 1 | 4 bits | 第一个Option Run包含的Option数量 |
| Number of Option 2 | 4 bits | 第二个Option Run包含的Option数量 |
| Service ID | 16 bits | 服务标识符 |
| Instance ID | 16 bits | 服务实例标识符 |
| Major Version | 8 bits | 主版本号 |
| TTL | 24 bits | 生存时间,单位秒 |
| Minor Version | 32 bits | 次版本号(仅Find Entry可选) |
嗯,这里要注意一个细节:Index 1st Option Run 和 Index 2nd Option Run 这两个字段,很多人一开始会搞混。它们指向的是Entry Array后面的Option Array里的位置。说白了,就是告诉接收方:“我的附加信息(比如IP地址、端口号)在Option数组的哪个位置开始读。”
重点:Service Entry里的Service ID + Instance ID + Major Version 这三个字段组合起来,唯一确定一个服务实例。我在项目中遇到过两次因为Instance ID配错导致服务找不到的bug,查了一整天,最后发现是配置文件里Instance ID写成了十进制而不是十六进制。
TTL字段也值得单独说说。它表示这个Entry的有效期。如果TTL=0,那就是StopOffer,告诉对方“我不再提供这个服务了”。如果TTL=0xFFFFFF,表示这个Entry永远有效。实际项目中,我建议TTL不要设太大,一般300秒(5分钟)比较合理,这样网络拓扑变化时能快速收敛。
4.2 Eventgroup Entry 字段解析
Eventgroup Entry是给订阅者用的。当某个ECU想订阅一组事件时,它就发一个Subscribe Eventgroup Entry。它的结构和Service Entry类似,但字段含义不同。
| 字段 | 长度 | 说明 |
|---|---|---|
| Type | 8 bits | 0x06=Subscribe, 0x07=StopSubscribe, 0x08=SubscribeAck |
| Index 1st Option Run | 8 bits | 同Service Entry |
| Index 2nd Option Run | 8 bits | 同Service Entry |
| Number of Option 1 | 4 bits | 同Service Entry |
| Number of Option 2 | 4 bits | 同Service Entry |
| Service ID | 16 bits | 服务标识符 |
| Instance ID | 16 bits | 服务实例标识符 |
| Major Version | 8 bits | 主版本号 |
| TTL | 24 bits | 生存时间 |
| Reserved | 12 bits | 保留位,必须为0 |
| Counter | 4 bits | 预留,通常为0 |
| Eventgroup ID | 16 bits | 事件组标识符 |
你看,Eventgroup Entry比Service Entry多了个Eventgroup ID字段。这个字段告诉服务端:“我想订阅的是你服务里的哪个事件组。” 一个服务可以有多个事件组,比如一个传感器服务可能有“温度数据组”、“湿度数据组”、“报警事件组”。
小技巧:我曾经在调试一个ADAS(高级驾驶辅助)项目时,发现某个ECU老是订阅不到摄像头数据。后来抓包一看,Subscribe Entry里的Eventgroup ID写成了0x0001,但服务端实际发布的是0x0002。这种低级错误,用Wireshark一过滤就现原形了。
另外,Subscribe Entry的TTL含义和Service Entry略有不同。订阅者发Subscribe时,TTL表示“我希望这个订阅持续多久”。服务端收到后,如果同意订阅,会回复一个SubscribeAck,里面的TTL是服务端实际允许的订阅时长。如果服务端不同意,就回一个SubscribeNack。
4.3 Entry Array 的排列规则
好,单个Entry看完了,咱们看看它们怎么排成数组。Entry Array是SOME/IP-SD消息的主体,所有Entry都按顺序放在这个数组里。
排列规则其实很简单,就三条:
- 先Service Entry,后Eventgroup Entry。 也就是说,同一个SD消息里,所有Service Entry必须排在前面,所有Eventgroup Entry跟在后面。不能穿插排列。
- 每个Entry固定16字节。 不管Service Entry还是Eventgroup Entry,长度都是16字节。这方便接收方用固定步长遍历数组。
- Entry Array后面紧跟着Option Array。 Entry里的Index字段指向的就是Option Array里的偏移位置。
为什么会这样设计?我个人理解是为了解析效率。接收方收到一个SD消息后,先解析头部,拿到Entry Array的长度,然后按16字节一个Entry往下读。读到第N个Entry时,如果发现Type是Eventgroup类型,就知道前面的N-1个都是Service Entry。这样解析逻辑非常清晰,不需要额外的标记位。
避坑指南:我曾经在一个项目中,因为Entry Array里Service Entry和Eventgroup Entry混着放,导致接收方解析出错。虽然有些实现可能容忍这种乱序,但按照规范,这是不合法的。记住:Service Entry必须在前面,Eventgroup Entry必须在后面,顺序不能乱。
另外,Entry Array里可以包含多个相同Service ID的Entry吗?可以。比如一个ECU可以提供两个不同Instance ID的相同服务,那它就会发两个Service Entry,一个Instance ID=1,一个Instance ID=2。接收方看到这两个Entry,就知道这个服务端同时托管了两个实例。
最后,我建议你在实际开发中,用Wireshark抓包看看真实的SOME/IP-SD消息。你会看到Entry Array里整整齐齐地排列着16字节的Entry,每个字段都清晰可见。嗯,纸上得来终觉浅,抓包看一次比读十遍文档都管用。
下一章咱们聊聊Option的格式,那是Entry的“附件”,里面藏着IP地址、端口号这些关键信息。到时候你会发现,Entry和Option配合起来,才是完整的服务发现。