深入浅出理解SOME/IP
SOME/IP,全称为Scalable service-Oriented MiddlewarE over IP,是一种面向服务的可伸缩的协议,用于控制消息的汽车中间件的解决方案。
SOME/IP于2011年由BMW设计,2014年纳入AUTOSAR规范。
SOME/IP的报文格式如下图所示,由消息头部(Header)和消息体(Payload)组成,Header主要包括以下字段:
-
Message ID,用于唯一标识消息,
如下图所示:
当消息为Method类型时,由Service ID和Method ID组成,
当消息为Event类型时,由Service ID和Event ID组成,
-
Length,消息长度(从Request ID开始到Payload结束);
-
Request ID,服务提供者和调用者可用于区分相同消息的不同调用,由Client ID和Session ID组成,如下图所示:
通常我们称服务提供者为Service,服务调用者为Client,Service ID和Client ID用于区分,一般会在一个SOA架构中统一地配置这些ID的数值。
这里插播一点个人理解,在SOA中,每个服务就好像我们每一个人在社会中扮演的角色,在对别人提供着服务的同时,同时也享受着别人提供出来的服务,人与人之间,既是彼此独立的,又是需要互相通讯的。服务提供者将功能具象为一组接口,这样使用者就能知道如何调用服务,完成某件事情,得到某个结果。
-
Protocol Version,协议头版本号,目前该值必须为1;
-
Interface Version,接口版本号,一般由服务提供者定义;
-
Message Type,用于标识消息的类型,如下图所示:
消息类型和通信机制之间的映射关系,如下图所示(灵魂画手,将就看吧),不难发现,Field结合了Method和Event,这也就理解了Message ID中为什么只有Method ID和Event ID,没有Field ID。Field可以用于实现这样一种通信场景:客户端希望能够获取/设置/监听服务端的某一个状态值,图中SOME/IP-SD。
-
Return Code,用于标识请求是否成功处理,不同的消息类型,它们在传输时所携带的Return Code也不同:
具体返回值和错误码定义如下:
Payload,也叫有效载荷,是消息内容,通常它的长度是可变的。
SOME/IP协议在OSI七层网络结构中位于应用层,它建立在TCP或者UDP传输层协议之上。
当通过UDP传输时,由于UDP的限制,Payload的长度应该限制在1400字节以内,超了则要分组(SOME/IP-TP)。
当通过TCP传输时,可以传输更多的字节,理论上只要不超过Length字段的大小即可。
对于AUTOSAR系统,Payload要遵循AUTOSAR规范进行序列化,
对于非AUTOSAR系统,可以遵循AUTOSAR规范进行序列化,也可以采用其他序列化方式如常用的Google Protocol Buffer、JSON等。
以上介绍了SOME/IP协议,可以发现,SOME/IP其实并不等同于SOA,只能说要实现SOA,SOME/IP是一个很不错的协议选择。
SOME/IP还有一个控制协议SOME/IP-SD,SOME/IP-SD也是基于SOME/IP的报文,用来实现服务发现和事件订阅机制。
SOME/IP-SD消息通过UDP进行传输,报文格式如下图所示:
Flags= 重新启动标志+单播标志+显示初始数据控制标志,如下图所示:
Flags=Reboot Flag + Unicast Flag + Explicit Initial Data Control Flag
服务重新启动后,所有消息的Reboot Flag须置为1,直到Session ID重新从1开始计数,之后的Reboot Flag须置为0。
Entries Array,Entry可以理解为“入口”,包含了服务实例以及需要订阅的事件组的信息,分为Service和Eventgroup两种类型,
一个SD报文可能包含多个Entry,每个Entry大小都是16个字节,一个Entry可能包含0-2个Option。下图为一个完整的SD报文示例:
Service Entry 用于服务发现:
-
Type:当网络中未收到相关服务的OfferService或者暂时未收到,而Client又需要访问该服务,那Client可以发出FindService去主动寻找服务,如果Service已经就绪的话,会回复OfferService报文;服务就绪后,主动发出OfferService,用以告知组播内其他节点,该服务已经启动,可以创建连接;
当服务不可用时,会主动发送StopOfferService报文,用以告知组播内其他节点,该服务目前不可用,停止发送请求,并取消订阅。
Type值 | 名称 |
---|---|
0x00 | FindService |
0x01 | OfferService |
StopOfferService |
-
Index 1st options:Option1排在Array里第几个
-
Index 2nd options:Option2排在Array里第几个
-
# of opt 1:Option1的数目
-
# of opt 2:Option2的数目
-
Service ID:Entry关于哪个服务
-
Instance ID:Entry关于服务的哪个实例,0xFFFF表示全部实例
-
Major Version:服务的主版本号
-
TTL:“入口”的生命周期,单位为秒,我理解为发现服务时的搜索时间,提供服务时的有效时间
-
Minor Version:服务的次版本号
服务发现,说白了,就是想办法让服务消费者能够找到服务提供者。
打个比方,想象你在一个有很多人的广场上找一个会唱歌的人,很显然有两种情况:
1. 你认识这个人,提前说好了,他站在某个地方等你,而你知道那个地方的位置,那你肯定很容易就找到他了,这就是静态配置;
2. 你并不认识这个人,存在一个中间人,你告诉中间人你想找一个会唱歌的,而那个人也会告诉中间人我是会唱歌的,我站在广场的哪个位置,然后中间人把位置给你,你就可以找到他了,这就是动态发现,而SOME/IP-SD就是那个中间人。
Eventgroup Entry 用于事件订阅:
-
Type:当Client收到服务OfferService之后,Client可以发送Subscribe报文主动跟Service订阅感兴趣的事件组;
当Client订阅某个事件组之后,后续发现不再需要改事件组的数据了,可以通过StopSubscribe报文来通知Service,避免不必要的数据交互;
当Service收到Client的Subscribe报文之后,需要先行判断是否符合可订阅的条件,如果该Client满足事件组订阅条件,则返回SubscribeAck,告知Client订阅成功,
当事件组内的事件准备就绪之后,Service会以某种约定好的形式发送相关事件给成功订阅的Client,
如果该Client不符合事件组订阅条件,那Service就会直接回复SubscribeEventgroupNack,告知订阅失败。
Type值 | 名称 |
---|---|
0x06 | Subscribe |
StopSubscribeEventgroup | |
0x07 | SubscribeAck |
SubscribeEventgroupNack |
-
Initial Data Requested Flag:如初始值由服务发送,须置为1
-
Counter:区分相同订阅者的订阅请求
-
Eventgroup ID:事件组ID,也就是说SOME/IP事件订阅和取消订阅的颗粒度到一个事件组,而不是一个事件
下面的示例,说明了一个Client发现服务和订阅事件组的过程:
Options Array,Option可以理解为选项参数,Type=0x01时,用于传输Entry的附加信息,比如服务名等等:
Type=0x04时,用于传输IPv4相关的参数,比如服务的IP地址、TCP还是UDP、端口号:
从下图可知,对于不同的消息,要配置的选项类型也不一样,甚至不需要配置,其他几种选项的具体内容不一一列举了。
到这里,SOME/IP算介绍完了。是不是觉得如果要自己实现SOME/IP全部的协议,还是有点复杂的。
目前GENIVI的vsomeip开源库已经实现了SOME/IP协议栈,所以通常并不用再去造轮子。
换言之,我们完全可以基于vsomeip开发SOME/IP应用程序,不用关心报文长什么样,也不用关心服务发现和事件订阅的细节,拿到手已经是Payload了,如果再用上GENIVI的CommonAPI,IDL一写,一条命令下去,代码自动生成了,Payload都用不着解析了,这样就实现了真正的RPC。
更多推荐
所有评论(0)