在整车功能开发以及调试过程中,ECU与ECU之间使用车载以太网的比例越来越多,所以区别于之前的CAN总线报文的,对于以太网数据包的抓取和分析越来越普及了,而其中又绕不开SOME/IP,每一个搞过车载功能开发都或多或少接触过或者使用过SOME/IP。我在了解这个协议过程中走了不少弯路,后来发现并没那么复杂,于是打算把自己的心得记录下来。
主要从3个角度记录我所学习的SOME/IP,首先是一些基本的知识,其次是怎样用Vector CANoe来调试一个结合了AUTOSAR通信,包括怎样在CANoe里边调整数据库实现自定义调试;最后做一个实践,使用开源的SOME/IP协议栈vsomeip,来和我们CANoe的SOME/IP协议栈做一个真实的通信。
SOME/IP基础知识
SOME/IP是基于TCP/UDP/IP协议之上的应用层协议,很像网络开发中前后端交互的HTTP的方式。如果之前做过前后端的开发,对于客户端和服务器的连接有一定的知识储备,那么在了解SOME/IP的过程中会觉得很简单的。SOME/IP依托于TCP或UDP,所有的数据都是作为TCP/UDP的Payload通过IP协议进行传输。可以理解成在Payload中又做了进一步的切分,并且比特位以及长度做了事先约定,例如下图中的前16个Bit固定为Service ID,紧接着的16个Bit为Method ID或者Event ID。
目前网络中常见的SOME/IP学习路径和学习资料,包括我之前看的一些,大多是在CAN总线技术路线以及实现方式的基础上介绍SOME/IP的特点以及技术细节的,很多从以太网IP以及UDP和TCP开始讲起,然后再说到SOME/IP的各种Header头部还有服务订阅等等内容,这样的一个学习路径,非常容易把很多的类似的概念参合到一起,比如IP层、UDP、TCP以及SOME/IP都有Header头部,IPv4和IPv6头部又不一样,TCP的三次握手四次挥手和SOME/IP的Subscribe订阅以及UDP和Notification,太太太容易混淆了。可能看了好久还觉得云里雾里,觉得这个太高深了,其实真的SOME/IP没啥,将其理解成ECU之间的HTTP类似协议,下层依赖的TCP/UDP和电脑手机服务器用的没有任何区别,明白这个就已经学会了一大半。关于车载以太网和普通以太网的区别,我在另外一个学习记录贴中有记录,一句话总结就是物理层数据链路有区别,到了IP层以上就完全一样了。
不过从另外一个角度也可以理解,很多汽车软件的开发人员是从传统技术栈比如CAN总线时代过来的,对于以太网的了解不像互联网人那样深入,所以有一定的需求再详细了解一些IP/TCP/UDP的知识。我的建议是将这两部分分开学习,先回顾一下计算机网络,七层协议啥的,再了解一个应用层协议比如HTTP,最后再详细学习SOME/IP,绝对会事半功倍,发现真的好简单,半天时间SOME/IP入门足够。
另外在车载应用开发中,SOME/IP的Payload部分填充,考虑到和其他ECU以及功能的兼容性问题,会使用AUTOSAR的规则定义方式,这和CAN总线的Frame帧非常的像。在CAN总线Data Field数据场一次最多传输8字节,通常会把这对应的64个比特拆分成传输数据的最小量,比如一个布尔型只分配1个比特,然后一个0-255的Uint8只分配8个比特,总共8字节64比特能拆分成很多个Signal信号,从而有限的资源传输更多数据,各个Bit对应的Signal信号长度以及数据类型的记录成一个表格,即得到了CAN总线数据库DBC。在车载SOME/IP开发中Payload部分多数情况下也沿用了类似的思路,所以在Payload部分解析也需要一些额外的信号矩阵,结合了SOME/IP支持的数据结构和AUTOSAR数据结构,就能组合出很多复杂的结构体,实现更多上层应用间的数据传输。
这样的实现方式也可以很好的复用迁移传统汽车行业稳定成熟的软件解决方案,在必要的情况下,只需要在传输层做一些技术更新,核心逻辑不需要任何变动,从而降低潜在的风险。但是这一块是互联网人来做汽车软件的话,可能就不是很熟悉了,尤其对于不常见的Bit位的数据拆分,再结合这大端小端数据类型,还有信号矩阵有Scale放大缩小以及Offset偏移,八成互联网人转过来的开发第一次会搞错。建议之前对于汽车总线了解较少的开发人员可以系统学习一下CAN总线以及结合项目了解一下AUTOSAR信号矩阵,可以避免很多低级的错误。
传统车载网络CAN总线在面向信号的数据传输中,发送方会根据自身需求(如数值更新或变更时)主动发送信息,而无需考虑网络中是否有接收方当下需要这些数据。与之不同的是面向服务的数据传输模式即发送方仅当网络中至少存在一个接收方需要该数据时才会进行传输。这种方式的优势在于避免了不必要的数据对网络及所有连接节点造成的负载。因此,面向服务的数据传输要求服务器必须通过某种方式获知哪些接收方正在等待其数据。在SOA架构中,服务是构成系统的基本单元,它代表了系统中的某个功能或操作。服务通过明确的接口与外界进行交互,实现了功能的封装和重用。SOA架构的核心就是服务,它通过将应用程序划分为一系列的服务来降低系统的复杂度,提高系统的灵活性和可维护性。在SOA中,服务是通过其接口被定义和访问。接口是服务与外界交互的桥梁,它定义了服务的输入、输出和行为规范。在SOA架构中,以太网通信作为服务的传输载体,负责在不同的ECU或服务之间传递数据和信息,根据AUTOSAR平台的规范与推荐,一般选择以太网应用层协议SOME/IP协议或者DDS协议作为跨域通信协议。
在SOME/IP协议中,服务接口被明确划分为三种类型: Event、Field、Method,每种类型都服务于不同的通信需求。
Event and Field Notification
服务器基于活跃订阅发送的内容可呈现两种格式:事件通知(Event Notification)和字段通知(Field Notification)。这两种格式的共同特性是事件驱动生成机制,但其数据结构存在本质差异:
- 事件通知Event:采用自包含的静态快照形式,其字段填充的属性仅反映事件触发时的瞬时状态,与历史事件无任何关联
- 字段通知Field:包含具有时序关联性的动态值,除当前值外还隐含历史状态。因此字段可扩展为包含Getter/Setter方法,允许客户端对目标内容进行读写操作。
需要特别说明的是,在SOME/IP协议中,事件总是以事件组(Event group)的形式进行组织,客户端仅能订阅整个事件组,而无法单独订阅组内某个特定事件。Field的Setter/Getter 方法遵循请求/响应(REQUEST/RESPONSE)模式,变更通知则通过事件机制实现,所有订阅操作都需通过SOME/IP服务发现(SOME/IP Service Discovery)协议完成。
Method
另一种数据交换方式是通过方法调用(Method Call)实现信息传递。客户端通过发起远程过程调用(Remote Procedure Call, RPC),触发目标服务器上的函数执行。具体流程如下:
- 调用过程:客户端通过网络发送请求来调用服务器函数,该请求可包含作为参数传递至被调用方法的数据;
- 响应机制:服务器执行函数后,可能通过响应消息向客户端返回执行结果。
需注意,当客户端主动调用函数时,通常意味着需要获取返回数据,通常被称作RR模式(Method Request with Response),但服务器方法也可能不返回任何值(Void类型),常被称作FF模式(Method Fire & Forget),此时客户端在确认方法被成功调用后即完成整个流程。
Service Discovery
为了让客户端能够获知当前可用的服务,SOME/IP-SD(服务发现协议)提供了两种动态服务发现机制:
- 服务提供(Offer Service):
- 服务器通过该机制向网络宣告其提供的所有可用服务
- 每个设备通过组播(multicast)方式广播消息
- 消息内容包含该设备提供的所有服务列表
- 传输层采用UDP协议
- 服务查找(Find Service):
- 客户端则可以通过该机制主动查询当前可用的服务
主要实现下列功能:
- 服务实例定位
- 服务运行状态检测
- 发布/订阅机制管理
其他SOME/IP-SD消息类型还包括:
- 事件组发布(Publishing an Event group)
- 事件组订阅(Subscribing an Event group)
根据标准要求,SD的头部的Service-ID和Method-ID是固定的,分别是0xFFFF和0x8100。至于是Offer Service还是Find Service,以及在实际中经常用到的Service服务版本,在标准中是在Entry内容体现的:
对于Offer Service/Find Service 来说的头部Entity,Type中用到的值有:
- 0x00:FindService
- 0x01:OfferService
- 0x02:StopOfferService
对于Eventgroup的发布和订阅用到的SD头部Entity,Type中用到的值有:
- 0x06: Subscribe Eventgroup Entry (订阅事件组条目)
- 0x07: Subscribe Eventgroup Ack (订阅事件组确认)
- 0x08: Subscribe Eventgroup Nack (订阅事件组否定确认)
- 0x09: Stop Subscribe Eventgroup Entry (取消订阅事件组条目)
另外一个对于SD常见的配置是IP端口号:30490,这是AUTOSAR官方建议的端口号。对于IP层服务以上关于SOME/IP-SD的要求,主要定义在《AUTOSAR_PRS_SOMEIPServiceDiscoveryProtocol.pdf》这份官方需求中,大家可以简单翻阅一下了解各大概。接下来记录一下,真正到了数据传输时,SOME/IP的Header以及Payload是怎么填充的:
首先SOME/IP的数据包分为Header和Payload两部分,而这两部分当作一个整体,作为TCP/UDP的Payload通过IP进行传输。如图所示,两个设备(A和B)之间通过SOME/IP协议进行通信:设备A向设备B发送一条SOME/IP消息,并接收一条回复消息。底层传输协议可采用TCP或UDP,这对消息本身没有影响。在此场景中,设备B运行着某项服务,设备A通过发送消息调用该服务功能,而返回消息即为服务响应。
头部报文结构:
- 服务ID(Service ID):每个服务的唯一标识符
- 方法ID(Method ID):0-32767 Method,32768-65535 Event,区别为Event的最高位为1
- 长度(Length):以字节为单位的有效载荷长度(包含后续8字节字段)
- 客户端ID(Client ID):ECU内部调用客户端的唯一标识(需保证整车范围内唯一)
- 会话ID(Session ID):会话处理标识(每次调用必须递增)
- 协议版本(Protocol Version):固定为0x01
- 接口版本(Interface Version):服务接口的主版本号
- 消息类型(Message Type):REQUEST (0x00) A request expecting a response (even void) — REQUEST_NO_RETURN (0x01) A Fire&Forget request — NOTIFICATION (0x02) A request of a notification/event callback expecting no response — RESPONSE (0x80) The response message — REQUEST_ACK (0x40) — NOTIFICATION_ACK (0x42) — ERROR (0x81) — RESPONSE_ACK (0xC0) — ERROR_ACK (0xC1) — UNKNOWN (0xFF)
- 返回码(Return Code):E_OK (0x00) No error occurred — E_NOT_OK (0x01) An unspecified error occurred — E_WRONG_INTERFACE_VERSION (0x08) Interface version mismatch — E_MALFORMED_MESSAGE (0x09) Deserialization error, so that payload cannot be deserialized — E_WRONG_MESSAGE_TYPE (0x0A) An unexpected message type was received (e.g. RE-QUEST_NO_RETURN for a method defined as RE-QUEST) — E_UNKNOWN_SERVICE (0x02) — E_UNKNOWN_METHOD (0x03) — E_NOT_READY (0x04) — E_NOT_REACHABLE (0x05) — E_TIMEOUT (0x06) — E_WRONG_PROTOCOL_VERSION (0x07) — E_UNKNOWN (0xFF)
Payload 序列化:
SOME/IP协议定义了很多序列化方式,消息格式通常与内存中的数据结构(紧凑型结构体)高度一致甚至完全相同,主要支持下列数据类型:
- 基础数据类型:
- 布尔值(Boolean):8位字段,0表示False,1表示True(禁止使用其他值)
- 无符号整数:Uint8(8位)、Uint16(16位)、Uint32(32位)、Yint64(64位)
- 有符号整数:Sint8(8位)、Sint16(16位)、Sint32(32位)、Sint64(64位)
- 浮点数:Float32(32位单精度)、Float64(64位双精度)
默认采用网络字节序即大端模式,也可配置成小端模式;
- 复合数据类型:
- 结构体(Struct)
- 字符串(String)支持大端变长模式
- 数组(Array):支持变长模式
- 枚举(Enumeration)
- 位(Bitfield):支持8、16和32Bit模式。
- 联合体(Union):
对于复合数据类型,长度/类型字段默认为32位,可配置为0/8/16/32位,所有长度/类型字段均采用网络字节序即大端模式。
接下篇:SomeIP学习笔记:使用CANoe调试/仿真SomeIP服务