SOA即面向服务架构。虽然,一开始我们对SOA面向服务架构的概念不了解,感觉很抽象。但是,当看到面向xxx的时候,我们很熟悉,讨论c++编程语言时,说c++是面向对象的编程语言。面相对象,那么对象作为一个基础的、核心的的概念,是c++的核心,c++中的概念都围绕着对象展开:类、抽象类、封装、继承、多态、模板等。那么对于SOA架构来说,则所有的概念都围绕着服务展开。
AP AUTOSAR标准基于SOA范式实现了通信模块(Specification of Communication Management),实现了面向服务的通信SOC(Service-Oriented Communication)。
本文以AP AUTOSAR(2311)通信规范文档为依据来讨论面向服务通信。
1SOC基本概念
面向服务通信,即通过服务这个基础概念把通信的概念涵盖进来。在自动驾驶中间价领域,即使我们完全没有听说过SOA的概念,当我们开发通信中间价的时候,我们常常需要开发如下功能:
(1)发布-订阅
一侧发布数据,一侧订阅数据,比如原生dds就支持这种语义。
(2)远程调用
远程调用,可以像调用本地函数那样调用远程的函数,可以有入参和返回值。
也可以向setter和getter类似的功能,远程设置一个属性或者获取一个属性。
SOC通过服务这个核心概念,把我们常用的一些通信范式进行了统一,SOC通过服务来提供发布-订阅、远程调用通信功能。当我们看到SOC的时候,完全不比纠结于面向服务到底是个什么概念,为什么叫面向服务,我们只需要知道面向服务,通过服务这个核心概念将发布-订阅、远程调用这些我们常见的通信范式进行了统一即可。

服务:
服务是核心概念,SOC中的所有概念都围绕着服务来展开。如上图所示,表示SOC的基本上示意图,有服务的提供者,服务的需求者。服务的提供者提供服务,服务的需求者查找服务,需求者找到服务之后便可以使用这个服务。
服务实例:
对于同一个服务,可以有一个服务端,也可以有多个服务端。每个服务端都是一个服务实例,服务实例用一个uint16类型的数据表示。

骨架和代理:
骨架又称为服务端,代理又称为客户端,类似于tcp/udp的服务端和客户端。服务端提供服务,客户端使用服务。
event:
event是发布订阅的通信范式。在SOC中,只能服务端发布数据,客户端订阅数据,不能翻过来。
trigger:
简化版的event,没有数据具体数据。类似于进程间通信的socket和信号量,socket可以传输具体的数据,信号量只是负责将线程唤醒。event类似于socket, trigger类似于信号量。服务端发送trigger,客户端接收trigger。
method:
method即方法,在c++或者java中,类中声明的函数,一般也叫方法。SOC里的方法和c++类中的方法概念是类似的,都是调用一个函数,可以传入参,可以有返回值,区别是c++中的方法是本地调用,SOC是远程调用。
服务端作为方法的提供者,客户端可以调用。
field:
field即字段。在c++中,当我们定义一个类的属性,常常把属性定义为private,如果有在类外部方位这个属性的需求,比如读和写,那么便会声明对应的setter、getter方法。SOC中的field与c++类中的setter、getter类似,可以通过setter和getter远程设置或读取该字段的值。
2基于dds实现SOC
2.1服务发现
服务发现是通信的前提,不管是event、method、field、trigger哪种通信方式,只有发现服务,才能够进行具体的通信。
2.1.1基于UserDataQosPolicy的服务发现
fastdds qos:UserDataQosPolicy-CSDN博客UserDataQosPolicy参考:fastdds qos:UserDataQosPolicy-CSDN博客
用DomainPaticipant来代表一个服务实例。当服务端OfferService的时候,创建对应的DomainParticiapant,并为DomainParticiapant设置UserData,UserData的格式按下图所示。前缀是ara.com://services/,后边是服务id,服务实例id,版本号。如果多个服务实例共用一个DomainParticipant,那么UserData也可以设置多个服务实例的信息。
客户端也应创建DomainParticiapant,并当发现新的DomainParticiapant的时候,读取发现的DomainParticiapant的UserData,来判断该服务实例是不是自己所要找的服务实例。

2.1.1.1PartitionQos
如果服务端有两个,实例id分别是1和2。客户端有两个,客户端1和客户端2,分别需要服务端1和服务端2,那么客户端1只需要发现服务端1的DataWriter和DataReader,客户端2只需要发现服务端2的DataWriter和DataReader。虽然1和2的topic相同,data type也相同,但是实际使用中客户端1不需要发现服务端2,客户端2也不需要发现服务端1。这种使用需求,可以通过PartitionQos来实现。
客户端:
针对发现的每个服务实例,都创建一个Publisher和SubScriber,PartitionQos配置为PartitionQos。

服务端:
服务端也需要创建一个Publisher和Subscriber,并配置PartitionQos。


2.1.3dds和SOC映射关系
1、当服务端调用OfferService
(1)用DomainParticipant代表一个服务实例
(2)针对每个event,创建一个topic和DataWriter
(3)针对每个trigger,创建一个topic和DataWriter
(4)针对method,创建一个request topic和一个reply topic,服务端要创建request topic的DataReader和reply topic的DataWriter
(5)field处理
①如果field中包含notifier,notifier与event类似,那么需要创建一个topic和DataWriter
②当hadGetter或hasSetter,创建一个request topic和一个reply topic,服务端要创建request topic的DataReader和reply topic的DataWriter
(6)为Domainparticipant设置user data
2、当服务端调用StopOfferService
(1)将user data从Domainparticipant的qos中移除
(2)移除在OfferService时创建的DataWriter和DataReader
3、当客户端调用FindService、StartFindService
(1)绑定一个BuiltinParticipantListener
(2)查找已经法相的DomainParticipant,根据客户端配置的关注的服务实例,向用户反悔对应服务实例的handle
4、当客户端调用StopFindService
解绑BuiltinParticipantListener
2.1.2基于topic的服务发现机制

基于topic进行服务发现,则需要内不创建一个topic ara.com://services/discovery,该topic的data type为ServiceAnnouncementMessage。每个进程都要创建该topic以及对应的DataWriter和DataReader。

ServiceAnnouncementMessage不是周期性发送,那么怎么保证新上线的DataReader能发现在之前上线的DataWriter发布的服务信息呢,需要按如下配置DataWriter的qos。

当identifier type设置为SERVICE_INSTANCE_RESOURCE_TOPIC_PREFIX,则服务端和客户端的Publisher和Subscriber不需要设置PartitionQos,而是会通过topic名来进行分区划分。实际使用的topic名格式如下:
ara.com://services/<InterfaceID>/<InstanceID>/<TopicName>

当identifier_type设置为SERVICE_INSTANCE_RESOURCE_PARTITION,则需要对Publisher和Subscriber设置Partition Qos。
2.2event

(1)struct名字在用户的名字之后加了后缀EventType
(2)struct字段增加了instance_id
2.3trigger

(1)struct名是固定的TriggerType
(2)struct成员只有一个,即实例id
2.4method


RequestHeader和ReplyHeader均是omg定义的规范https://www.omg.org/spec/DDS-RPC/1.0/PDF。RequestHeader终版instanceName即服务实例的实力id,requestId是DataWriter的guid。


2.5field

field中的notifier与event类似,只不过struct命名有区别。。


setter、getter使用的data type,与method类似。同样的,struct命名有区别。