SOMEIP ServiceDiscovery流程整理

SD的阶段

​ Service Discovery分为4个阶段:
Down Phase : 服务不可用(服务未注册/网络异常)
Initial Phase : 服务初始化阶段,不响应收到的FIND/OFFER报文
Repetition Phase:重复阶段,按照设置的间隔和次数发送FIND/OFFER报文,到达次数或者收到FIND/OFFER报文后进入
Main Phase:Server定时发送OFFER报文,而Client则响应OFFER报文(进行订阅回复)
有一个地方需要明确的是,这个SD的阶段,针对的是routingmanagerd这个进程(或者说具备Service Disovery功能的someip应用),而不是针对具体某个someip service。

SD的状态变迁

SOMEIP 服务的Server端

Down Phase:

​ 没有调用Application的offer_service接口发布服务,服务不可用

Initial Phase:

​ 调用了Application的offer_service接口。

​ 在此阶段,服务处于初始化的阶段,即使收到外部Client的FIND报文,也不对外发送OFFER报文。

​ 根据配置中的initial_min和initial_max配置值,在之间取一个随即值作为initial phase阶段的具体时长,如果想要固定这个时长,就将initial_min和initial_max配置为同一个值。

​ 当到达了initial phase阶段的时长后,发送第一包该服务的OFFER报文,然后进入Repetition阶段。

Repetition Phase:

​ 在该阶段,将重复发送OFFER报文,发送的次数和发送的间隔在配置文件中配置,而且,每一次发送的间隔会笔上一次延长一倍。

​ 例如,配置了repetition阶段发送3次OFFER,发送DELAY为100毫秒,则在此阶段的三包OFFER报文的发送间隔是

​ OFFER -> 200毫秒 -> OFFER -> 400毫秒 -> OFFER -> 800毫秒 -> OFFER,就是总发送包数是配置的+1, 发送间隔以配置为base,每次翻倍。

​ 在此阶段收到Client发送的FIND报文,将使用单播通信单独发送OFFER报文作为回应,这次发送会有一个延时,根据配置中的REQUEST_RESPONSE_DELAY决定)。

​ 在此阶段收到Client发送的SUBSCRIBE报文后,将使用单播通信发送SUBSCRIBE_ACK/NACK报文进行回复,并且开启订阅Entry的TTL计时。

​ 当到达了repetition phase阶段的时长后,进入Main阶段。

Main Phase:

​ 在该阶段,将按照配置的周期进行OFFER报文发送(在cyclic_offer_delay配置项中)

​ 在此阶段收到Client发送的FIND报文,将使用单播通信单独发送OFFER报文作为回应,发送的延时根据配置中的REQUEST_RESPONSE_DELAY决定)。

​ 在此阶段收到Client发送的SUBSCRIBE报文后,将使用单播通信发送SUBSCRIBE_ACK/NACK报文进行回复,并且开启订阅Entry的TTL计时。

SOMEIP 服务的Client端

Down Phase:

​ 没有调用application的request_service接口请求服务。

Initial Phase:

​ 调用了application的request_service接口。

​ 此阶段的持续时间和SERVER端的initial一致,在配置的initial_min和initial_max之间选取随机值作为该阶段的持续时间。

​ 在此阶段,如果收到SERVER端发送的OFFER报文,则直接跳过Repetition阶段,进入Main阶段。

​ 当到达了initial 阶段的时长后,将发送第一包FIND报文。

Repetition Phase:

​ 在此阶段,定周期发送FIND报文,发送次数由配置中的repetitions_max参数决定,发送的间隔规律和SERVER端repetition阶段OFFER报文的发送规律一致。

​ 在此阶段,收到SERVER端的OFFER报文,则进入Main阶段。

​ 在此阶段,收到SERVER端的STOP OFFER报文,则进入Main阶段。

​ 当到达repetition阶段的时长后(发送了配置中的FIND报文次数),进入Main阶段。

Main Phase:

​ 在这个阶段,主要发送订阅报文/取消订阅报文,并且不是定周期发送,而是在收到OFFER/STOP OFFER报文后发送。

​ 在此阶段,收到SERVER发送的OFFER报文,如果CLIENT有订阅其中的事件/事件组,则发送SUBSCRIBE报文

​ 在此阶段,收到SERVER发送的STOP OFFER报文,如果CLIENT有订阅其中的事件/事件组,则发送STOP SUBSCRIBE报文

SD配置

​ Service Discovery的配置一般在routing应用的json配置文件中,也就是当前主机中担当了路由角色的那个SOMEIP应用的配置文件。

​ 配置的内容如下:

"service-discovery" :
{
    "enable" : "true",   // 是否开启Service Discovery,如果不开启,就无法在域间进行路由(发布和请求服务,订阅事件)
    "multicast" : "239.192.255.251",    // Service Discovery报文主要依赖组播地址通信,这里就是配置的SD组播地址
    "port" : "30490",     // SD组播通信的端口,用于发送接收SD报文(OFFER/FIND/SUBSCRIBE)
    "protocol" : "udp",   // SD通信协议,这个好像没法选吧,走组播难道不用UDP?
    "initial_delay_min" : "50",    // 对应initial阶段时长的min值
    "initial_delay_max" : "50",    // 对应initial阶段时长的max值,最终的时长会在min和max中取随机值
    "repetitions_base_delay" : "100",  // repetition阶段OFFER/FIND报文发送间隔的base值,最终的发送间隔回是200/400/800
    "repetitions_max" : "3",   // repetition阶段OFFER/FIND报文的发送次数(3+1)=4
    "ttl" : "16777215",    // OFFER报文中SERVICE entry的TTL值
    "cyclic_offer_delay" : "2000",    // MAIN阶段OFFER报文的定周期发送间隔
    "request_response_delay" : "1000"    // SERVER端收到FIND报文后回复OFFER报文的延迟时间(不是立刻回复)
},

代码实现

​ 在vsomeip中,Service Discovery的功能主要在service_discovery_imp类中实现,其主要结构如下:
在这里插入图片描述

类:

request:

​ 用于路由程序(routingmanagerd)保存当前域内收到的其他client发起的someip service的请求(REQUEST SERVICE)的次数。

​ 其中,sent_counter_成员的数量在每次收到的其他client的REQEUST SERVICE后都会加1。

requests_t:

​ map容器,用于存放服务实例(service/instance/major/minor)和对该服务实例的请求信息(request)

serviceinfo:

​ 代表SOMEIP服务实例的基本信息。

​ 其中,service_, instance_, major, minor用于标记服务ID, 实例ID以及版本信息。

​ **reliable_unreliable_**是该服务对外通信使用的服务端endpoint,分别用于TCP和UDP,端口号根据配置文件中该service的配置决定,例如:

{
    "unicast" : "198.18.2.252",
    "netmask" : "255.255.255.0",
    "network" : "vsomeip",
    "applications" :
    [...],
    "routing" : "routingmanagerd",
    "service-discovery" :
    {...},
	"services" : [
        {
            "service" : "0x1111",
            "instance" : "0x2222",
            "reliable" : { "port" : "52006", "enable-magic-cookies" : "false" },   // 服务对外通信的TCP端口
            "unreliable" : "52006"				// 服务对外通信的UDP端口
        }
    ]
}

​ **requesters_**是一个client_id的集合,保存了所有对请求了该service的application应用的client_id,client_t类型是个整形,对应的是smeip application的id,每个someip application都有一个id和name,在配置文件中配置,如下:

{
    "unicast": "198.18.2.11",
    "netmask" : "255.255.255.0",
    "network" : "vsomeip",
    "applications" :
    [
       {
            "name": "hello_world_service",    // application名称
            "id": "0x4444"                    // application的client_id
        },
        {
            "name": "hello_world_client",     // application名称
            "id": "0x5555"                    // application的client_id
        }
    ],
    "routing" : "routingmanagerd"
}

​ **is_local_**标记所代表的someip服务在当前域中的someip应用发布的还是其他域的someip应用发布的。

​ **is_in_mainphase_**标记所代表的someip服务当前是否处于Main Phase阶段。

services_t:

​ 保存服务和服务实例的map容器(一个service可以有多个instance)

endpoint:

​ 通信接口类,定义了通信终结点的对外接口。

endpoint_impl:

​ endpoint接口类的实现类,是后面各种用途的endpoint的父类,其内部成员如下:

endpoint_host_:指向endpoint容器,endpoint容器是endpoint_manager_impl类实例。

routing_host_: 指向routing_manager_impl(在非路由routingmanagerd应用中,指向routing_manager_client)。

is_support_someip_tp_:标记是否支持TP操作,对于UDP通信,因为存在发送payload的大小超过MTU,因此需要进行拆包发送,到达对端后进行组包,被拆分的包就是TP包。

configuration_: 指向someip配置对象(从VSOMEIP_CONFIGURATION环境变量配置的文件中读取)

event:

​ event代表事件组中的某个事件的信息,主要内部成员如下:

​ current_: notification类型的someip消息

​ update_: notification类型的someip消息,周期发送的时候发送的是该someip消息

​ cycle_timer_: 发送该事件notification消息的定时器

​ eventgroups_: 该事件所属的所有事件组集合

​ refs_: 对于该事件有订阅关系的someip应用的client_id (std::map<client_t, std::map<bool, uint32_t>>)

​ pending_: 当需要将事件的消息单独发给某个someip应用时,就将和该someip应用通信的endpoint的信息添加到pending_中。

eventgroupinfo:

​ eventgroupinfo是事件组的信息,包含成员如下:

service_, instance_, instance_, major_:eventgroup所属的服务信息。

​ **ttl_****:**事件组的生存期,在该周期内事件组有效。

address_, port_: 事件组在该地址:端口上对外发布数据。

event_: 事件组包含的事件的集合。

subscriptions_: 对该事件组的远端someiip应用的订阅信息。

subscription:

​ 对SOMEIP的服务实例(instance)的某个事件组(eventgroup)的订阅信息,包括本地有哪些someip应用订阅了该事件组(保存了其application的client_id)以及用于和事件组进行通信的client endpoint。

reliable_: 和所订阅事件组通信的tcp client endpoint

unreliable_: 和所订阅事件组通信的udp client endpoint

clients_: 订阅了该事件组的someip应用的client_id和订阅状态的集合(std::map<client_t, subscription_state_e> clients_)

eg_info_: 事件组信息

remote_subscription:

​ 对应单个远端IP主机上的routingmanagerd应用对本机上的SOMEIP的服务实例(instance)的某个事件组的订阅信息,主要成员:

id_: 主要用于在eventgroupinfo的subscriptions_中找到自己(remote_subscription),subscriptions_的定义如下:

std::map<remote_subscription_id_t, std::shared_ptr<remote_subscription>> subscriptions_;

is_initial_: 标记该远程订阅信息是否被事件组处理过(add/update_remote_subscription)。

parent_: 更新后指向上次的订阅信息 (例如本次订阅相比上一次订阅的application的client_id集合发生变更,就需要使用一个新的remote_subscription来标识) 。

eventgroupinfo_: 指向所订阅事件组的eventgroupinfo。

ttl_: 用于设置每个client的订阅的有效期 (从当前时间 + ttl_作为该客户端对该事件组订阅的超时时间点)。

counter_: 没有在代码中找到使用的地方,用途未知。

clients_: 在事件组上产生订阅关系的所有远端someip应用的client_id集合, client_id的定义如下:

std::map<client_t,      // 订阅该事件组的someip应用的client_id
	std::pair<
		remote_subscription_state_e,      // 订阅状态
		std::chrono::steady_clock::time_point      // 该订阅的超时时间点
	>
> clients_;

subscriber_: 远端SOMEIP应用用于接收订阅回复消息(SUBSCRIBE_ACK/SUBSCRIBE_NACK)的地址和端口。

​ 对于订阅eventgroup的someip应用来说,其订阅报文中包含最多2个option子消息,每个option中包括一个tcp/udp的endpoint definition。subscriber_代表的是其中最后一个有效的通信endpoint。

reliable_: 订阅报文中eventgroup_entry所带options中reliable类型的endpoint_definition。

unreliable_: 订阅报文中eventgroup_entry所带options中unreliable类型的endpoint_definition。

answers_: 每次给remote_subscription对应的远端routingmanagerd的订阅进行回复(ACK/NACK)后,该answers_值都会+1。

service_discovery_impl:

​ routingmanagerd进程中主管SD(service discovery)功能的模块,负责发送/接收SD报文,管理事件订阅/反订阅等。

runtime_: 指向application运行时的指针,application运行时是一个SOMEIP进程中所有someip application的容器和创建者。

requested_: 保存routingmanagerd进程收到的本域其他someip应用对外域someip service的请求信息。

subscribed_: 保存routingmanagerd进程收到的本域其他someip应用对外域someip eventgroup的订阅信息。

std::map<service_t,
	std::map<instance_t,
             std::map<eventgroup_t,
                	  std::shared_ptr<subscription>
            		 >
        	>
> subscribed_;

sd_multicast_: SD功能用到的组播地址(OFFER/FIND报文的通信地址),在配置文件中有设置。

sd_multicast_address_: 同sd_multicast_,只是类型为asio::ip::address,用于创建server_endpoint。

unicast_: 单播地址,对应的是加入SOMEIP组播地址的那张网卡的IP地址(加入组播后才能收到SD消息)

port_: SD报文通信使用的端口,如果没有配置就是30490,也就是说,发送SD报文时,source为:unicast_ + port_ ,destination为: sd_multicast_ + port_。

last_msg_received_timer_: 定时器,用于监视最后一次从组播收到OFFER消息的时间是否超过了设定的超时时间,如果长时间没有收到OFFER报文,则会重新执行将网卡加入组播地址的动作。

endpoint_: 在SD的组播地址上创建的udp_server_endpoint_impl对象,用于在组播地址上发送和接收SD报文(OFFER/FIND/SUBSCRIBE)。

initial_delay_: initial阶段的持续时间 (在配置的initial_delay_min和initial_delay_max的范围之间取随机值)。

collected_finds_: 代码中未使用到,用途未知。

collected_offers_: routingmanagerd进程中用于存储本域内对发offer的所有someip service的serviceinfo集合。

repetitions_max_: repetiton阶段发送FIND/OFFER报文的最大次数(从配置文件读取)。

repetitions_base_delay_: repetition阶段发送OFFER/FIND报文的起始间隔。

cyclic_offer_delay_: 进入main阶段后发送OFFER报文的周期时间间隔。

offer_debounce_timer_: initial阶段定时器,在结束initial阶段后发送第一包OFFER报文。(注:如果在定时器结束时,本机没有offer任何服务,那么还会停留在Server的initial阶段,重复该定时器,因为OFFER报文里面ServiceEntries不应该是空的)

find_deounce_timer_: initial阶段定时器,在结束initial阶段后发送第一包FIND报文。(注:如果在定时器结束时,本机没有request任何服务,那么也还会停留在Client的initial阶段,重复该定时器,因为FIND报文里面ServiceEntries不应该是空的)

repetition_phase_timers_: SERVER端(offer service)的repetition阶段定时器,定时器会执行多次 ( > repetitions_max_ ) ,每次的定时间隔会是 2^N * repetitions_base_delay_数(N是次数)。

find_repetition_phase_timers_: CLIENT端(find service)的repetition阶段定时器,定时器执行册数和间隔同repetition_phase_timers_。

main_phase_timer_: main阶段定时器,按照cyclic_offer_delay_配置的间隔,定时启动该定时器发送OFFER报文,直到程序退出时取消该定时器。

last_msg_received_timer_: 监视组播上发送数据的定时器,如果在定时器间隔内,没有收到组播上的消息,则认为网络可能存在问题,会执行将网卡加入组播地址的动作。这个定时间隔等于cyclic_offer_delay_ + (cyclic_offer_delay_ / 10)。

ttl_factor_offers_: ttl_map_t 类型成员,保存了每个SOMEIP服务实例的TTL系数,例如设置系数为3,然后服务实例的TTL为5秒,那么当OFFER报文中该服务实例缺失15 (5 * 3)秒后,CLIENT端才会判定该服务实例UNAVAILABLE不可用。

typedef std::map<service_t, std::map<instance_t, ttl_factor_t>> ttl_map_t;

ttl_factor_subscriptions_: ttl_map_t 类型成员,保存了每个服务实例上事件组的订阅的TTL系数,例如设置系数为3,当订阅的TTL为5秒时,如果SERVER在最后一次收到CLIENT的订阅报文的15 (5 * 3)秒后仍然没有收到新的订阅报文,那么SERVER就判定该订阅实效。

pending_remote_subscriptions_: remote_subscription和remote_subscription_ack的建值对集合

std::map<std::shared_ptr<remote_subscription>,
         std::shared_ptr<remote_subscription_ack>
> pending_remote_subscriptions_;

​ 在订阅/取消订阅(SUBSCRIBE)报文中,存在多个eventgroupentry项目,每个项目对应的是一个service/instance/eventgroup的订阅/取消订阅,作为key的remote_subscription就对应了具体某一个eventgroupentry的信息,其中包括了TLL(用于标识是订阅/取消订阅)以及该eventgroupentry附带的一个/两个option中包含的对端routingmanagerd发起订阅使用的client_endopint绑定的地址和端口。

​ 作为someip服务的服务端,在收到订阅/取消订阅的报文后,无论其中有多少eventgroupentry项目(每一个entry条目包含对一个服务实例的某个事件组的订阅,而entry type=6),作为value的remote_subscription_ack就代表了对订阅报文的响应报文。

​ 因此从**pending_remote_subscriptions_**的结构可以看到,每个remote_subscription对应一个remote_subscription_ack(每一个eventgroup的订阅都有一个订阅回复ACK),而remote_subscription_ack中subscriptions_成员包含了多条remote_subscription(因为每次订阅回复的ACK报文中,包含多个订阅evengroupentry对应的remote_subscription)。

时序:

SOMEIP SERVER端:
init阶段:

在这里插入图片描述

​ 在service_discovery_impl中,offer_debounce_timer_定时器是负责Initial wait阶段的工作的,这个定时器按照initial_delay_配置值周期运行,每次超时的时候,就会收集这段时间内本域内其他someip application发布的service instance(在collected_offers_中),生成一个Offer Message对外发布,同时启动offer_debounce_timer_定时器的下一轮定时,这里也就完成了initial wait阶段结束才完成新发布service instance的第一次offer动作的要求。

​ 当一个someip service的instance处于initial wait阶段的时候,即使收到了外域的routingmanagerd的find报文,也不会做响应(发布offer报文做为回应),这个是怎么实现的呢?(to-do)

repetition阶段:

#####

​ repetition定时器会有很多个,每当offer_debounce_timer_超时时有新的service instance要进行offer就会创建一个新的repetition timer放到repetition_phase_timers_,每个repetition定时器会和该定时器到期时要offer的service instance进行绑定,在service_discovery_impl中,repetition定时器是保存在repetition_phase_timers_这个map容器中的:

std::map<std::shared_ptr<boost::asio::steady_timer>,
            services_t> repetition_phase_timers_;   // timer和services的配对集合

​ 每个repetition定时器会被重复触发多次(根据repetitions_max配置),每次触发的延时时长会从最开始的repetition_base_delay开始,每次翻倍。

​ 每个repetition定时器被触发时,会将绑定该定时器的service instance集合组成OFFER报文发送出去,到该repetition定时器触发次数到达repetitions_max后,会将这些service instance设置为in_mainphase状态,标识这些service实例处于main阶段。

main阶段:

​ mian阶段定时器在SD模块启动后就启动了,定时发送经过了repetition阶段的service instance的OFFER报文(当repetition定时器完全结束其触发次数后,会将绑定的serviceinfo中in_mainphase设置为true,标识该service instance当前处于main阶段)。

发布服务:

​

​ routingmanagerd通过unix domain socket收到本域其他someip应用offer的service instance后,会将service info保存到service_discovery_impl的collected_offers_容器中,然后由offer_debounce_timer_定时从该容器中收集新的service instance 组成这些新的service instance的第一包OFFER报文进行发送。

处理订阅:

​

​ 处理订阅流程的源头来自于收到的SD报文中的subscribe eventgroup entry,service_discovery_impl将每一条eventgroup entry的订阅对应一个新的remote_subscription对象A,然后将该remote_subscription对象交给routing_manager_impl处理。

​ remote_subscription对象A中包括订阅的eventgroupinfo,发起订阅的远端someip application使用的client endpoint(reliability/address/port),routing_manager_impl在处理该remote_subscription对象时,会查看eventgroupinfo中,发起订阅的远端someip application是否之前已经对订阅的事件组有过订阅记录,如果之前存在订阅记录,也就是eventgroupinfo中已经存在一个remote_subscription对象B和remote_subscription对象A是同一个远端someip application对相同事件组的订阅,并且remotesubscription对象B的订阅请求,routingmanagerd还未给对方回复ACK/NACK,那么会将remote_subscription对象A的父订阅记录设置为remote_subscription对象B

​

​ 这里有一个地方需要注意的是,一般来说我们不太使用selective subscribe,也就是所有的订阅操作都是以routingmanagerd的client_id (VSOMEIP_ROUTING_CLIENT) 来对外域的eventgroup进行订阅的。因此,外域很少会收到前后client_id不同的订阅报文。如果需要指明是本域的那个someip application订阅外域的eventgroup,那么在订阅报文的eventgroup entry中会由字段来标识订阅applications的client_id。

​ 对于还没有回复ACK/NACK报文的订阅记录,其对应的remote_subscription对象都会存放在service_discovery_impl的**pending_remote_subscriptions_**成员中。

回复订阅:
someip client端:

​ 回复订阅由发布所订阅事件组的someip应用发起,在通过UDS socket收到routingmanagerd进程转发的对事件组的订阅请求后,判断是否接受订阅,然后还是通过UDS socket将订阅回复信息(subscribe_ack_command)发给routingmanagerd进程,大致流程如下图:

​

routingmanager端:
image-20241227145652906

​ 每个remote_subscription对象对应之前SUBSCRIBE报文中的某一条eventgroup entry,每个remote_subscription_ack对象对应该SUBSCRIBE报文的ACK回复报文。

​ routingmanagerd进程收到收到subscribe_ack_cmd后,routing_manager_impl首先会标记对应remote_subscription对象的状态为ACK,然后由service_discovery_impl判断是否可以回复SUBSCRIBE_ACK报文了(SUBSCRIBE报文中所有eventgroup entry的remote_subscription对象都是ACK的状态)并且发送SUBSCRIBE_ACK报文,流程如下:

SOMEIP CLIENT端:
init阶段:
someip client端:

routingmanagerd端:

repetition阶段:

#####

订阅:

处理订阅回复:

main阶段:

​ 对于client端来说,其main阶段的动作是本地routingmanagerd进程收到OFFER报文后,回复对端routingmanagerd以SUBSCRIBE报文(如果本地有someip应用订阅了该OFFER报文中的事件组)。

Clinet应用获取client_id

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值