1 vsomeip简单介绍
vsomeip 是 GENIVI 实现的开源 SOME/IP 库,由 C++ 编写,目前主要实现了 SOME/IP 的通信和服务发现功能,并在此基础上增加了少许的安全机制。
如图所示,vsomeip 不仅涵盖了设备之间的 SOME/IP 通信(外部通信),还涵盖了内部进程间通信。两个设备通过communication endpoints(通信端点)进行通信,endpoints 确定传输使用的协议(TCP 或 UDP)及端口号或其他参数。所有这些参数都是可以在 vsomeip 配置文件中设置的(配置文件是 json 格式)。内部通信是通过本地 endpoints 完成的,这些 endpoints 由 unix 域套接字使用 Boost.Asio 库实现。由于这种内部通信不是通过中心组件 (例如 D-Bus 守护进程) 路由的,所以它非常快。
中央 vsomeip 路由管理器(routing manager)只接收必须发送到外部设备的消息,并分发来自外部的消息。每个设备只有一个路由管理器,如果没有配置,那么第一个运行的 vsomeip 应用程序也会启动路由管理器(这里的条件是在于没有配置主routing manager)。
2 vsomeip服务发现前的通道建立流程
在片内vsomeip通信框架中,通信节点分为路由节点(routing manager,即图中的master)和附属节点(即图中slave节点),vsomeip节点之间能够通信的前提是片内存在一个路由节点,即路由节点是中心节点,以下是路由节点在通信中的作用:
- 为片内每个vsomeip通信节点分配一个客户端ID号,即client_id,并将client_id与应用程序名称关联
- 注册片内每个vsomeip通信节点的应用程序
- 发送已注册应用程序的路由信息给该应用程序,使其能够创建接收连接的vsomeip通道,通过创建unix socket并监听于该socket,等待连接,通道的建立基于文件/tmp/vsomeip-${clinet_id}
- 接收并保存vsomeip通信节点发布的服务
- 处理来自vsomeip通信节点event的注册以及订阅
- 处理来自vsomeip通信节点请求服务(REQUEST_SERVICE)和释放服务(RELEASE_SERVICE)
- 处理来自vsomeip通信节点method的请求(REQUEST)与回应(RESPONSE)
- 处理节点间与安全相关的策略,例如E2E配置信息等
- 接收并分发来自其他设备的服务信息
在vsomeip通信中,master节点要先于其他节点启动,如果配置了master节点而未启动,则片内所有vsomeip节点,均无法通信,如果未配置master节点则,第一个启动的vsomeip节点,即是master节点。vsomeip通信通道建立流程如下:
- master节点启动,并通过/tmp/vsomeip-0文件,创建unix socket并监听于该socket,该socket是已知的docket,类似于网卡中已知的广播地址,后续的启动的vsomeip通信节点都会通过/tmp/vsomeip-0文件,向master节点发起connect链接
- slave节点启动,通过/tmp/vsomeip-0文件向master发起connect操作,建立通信连接
- slave节点connect成功之后,回调connect_cbk函数,在该函数中,通过已建立的/tmp/vsomeip-0 unix socket通道,发送VSOMEIP_ASSIGN_CLIENT命令,并携带应用程序ClientID和名称,
- master节点接收到VSOMEIP_ASSIGN_CLIENT命令后,检测接收到ClientID和名称,如果注册过,这使用该ClientID并保存;如果注册过,则需要分配一个ClientID给该应用程序
- master节点通过VSOMEIP_ASSIGN_CLIENT_ACK命令,返回master节点为应用程序注册的ClientID号
- slave 节点接收到VSOMEIP_ASSIGN_CLIENT_ACK命令后,获取clientID并保存。随后通过VSOMEIP_REGISTER_APPLICATION命令,向master节点注册当前的应用程序,并传入分配的ClientID
- master节点接收到VSOMEIP_REGISTER_APPLICATION命令时,获取命令中携带的ClientID信息,并通过VSOMEIP_ROUTING_INFO命令返回ClientID
- slave在接收到VSOMEIP_ROUTING_INFO命令,通过文件/tmp/vsomeip-${ClientID} 创建unix socket通道并监听于该socket,用于等待其他通信节点的链接
3 服务发现流程
以上是一般的服务发现流程,slave1为service 发布方,slave为服务的订阅方
- 在通信通道建立之后,slave1 通过vsomeip提供的接口offer_event发送VSOMEIP_REGISTER_EVENT命令,并携带service_id, instance_id,event_id,event_group_id,major_version,minor_version等信息,向master注册event
- master收到VSOMEIP_REGISTER_EVENT命令后:
- 保存该event相关的信息
- 如果master启用了服务发现机制,则将该event消息通过广播或者单播发现给片间的其他各个主节点
- slave1 通过vsomeip提供的offer_service接口发送 VSOMEIP_OFFER_SERVICE命令,携带了 service_id,instance_id等信息
- master 接收到slave1的VSOMEIP_OFFER_SERVICE命令后:
- 保存该service信息
- 如果master启用了服务发现机制,则将该service消息通过广播或者单播发现给片间的其他各个主节点
- 检查当前master内部的订阅订阅信息,如果与该服务匹配,则发送订阅信息
- 调用master节点内部的on_offer_service函数,通知匹配的服务请求者
- 调用master节点内部的on_availability函数通知本节点当前服务可用
- slave2 通过vsomeip提供的API接口request_service发送VSOMEIP_REQUEST_SERVICE命令,并携带service_id,instance_id信息
- master节点在收到slave2的VSOMEIP_REQUEST_SERVICE命令后,调用master内部的handle_requests函数处理函数请求者
- 保存服务请求者的信息
- 根据服务请求者提供的service_id和instance_id,寻找匹配的服务提供端,
- 如果找到服务提供方,则向发送service 发布方,发送订阅方的路由信息,即发送VSOMEIP_ROUTING_INFO给发送方,即slave1,service发送方接收到VSOMEIP_ROUTING_INFO后,链接对端的unix socket,建立通信通道。另外master写订阅方也发送VSOMEIP_ROUTING_INFO命令,该命令中会携带service_id,instance_id,event_id以及发送端的client_id等信息,订阅端接收到这个信息后,通过发送端的client_id,建立unix socket通信通道
- 如果没有找到服务提供信息系,则跳过
- 订阅端slave2接收发送端路由信息后,根据master发送的VSOMEIP_ROUTING_INFO中携带的发送端client_id,以及event相关的信息,发起VSOMEIP_SUBSCRIBE命令,向service 发送端slave1进行订阅
- slave1收到VSOMEIP_SUBSCRIBE命令, 根据命令中event信息进行匹配,同时保存订阅者信息包括client_id,service_id,instance_id等,另外slave1发送命令VSOMEIP_SUBSCRIBE_ACK/NACK,对订阅请求进行回应.
4. 消息发送
vsomeip消息通信的是建立于服务发现的基础之上,相对vsomeip服务发现,消息通信相对来说流程比较简单:
- 服务发送方(skeleton),首先序列化待发送的数据结构,即serialize struct,存放于someip发送的消息的数据结构payload,该结构主要使用vector容器存放序列化后的数据。注:vsomeip内部不会对发送的数据结构作序列化操作,即vsomeip 发送的event数据,都是以event裸流数据发送
- 调用vsomeip提供的消息发送接口 notify
- notify内部会对payload再一次序列化,会将与event相关的信息序列化到event消息的头部,包括:session_id,service ,instance,等信息
- 根据skeleton内部保存的订阅数组,遍历以VSOMEIP_SEND命令发送每一个event的订阅端,即proxy1和proxy2。
- 订阅端proxy在收到VSOMEIP_SEND消息后,首先反序列化接收数据的头部信息,即service,instance,session_id等,并检验消息的正确性
- 调用proxy内部的on_message函数,触发event注册的回调函数。event事件的回调函数执行main_dispathc线程中,注:在vsomeip通信中,主要会启动三个线程:main_dispatch,io,以及shutdown线程,
5. vsomeip的序列化
vsomeip消息的发送其实都是裸流数据发送的,即uint_8* buffer,size_t length中形式发送的,在vsomeip中,会将这两个变量封装到一个payload中。以下为vsomeip提供的消息发送接口以及对应的存放消息的数据结构源码
//创建回应消息结构体
std::shared_ptr<message> runtime_impl::create_response(
const std::shared_ptr<message> &_request) const {
std::shared_ptr<message_impl> its_response =
std::make_shared<message_impl>(); // 这里创建的是message_impl,而基类是message
its_response->set_service(_request->get_service());
its_response->set_instance(_request->get_instance());
its_response->set_method(_request->get_method());
its_response->set_client(_request->get_client());
its_response->set_session(_request->get_session());
its_response->set_interface_version(_request->get_interface_version());
its_response->set_message_type(message_type_e::MT_RESPONSE);
its_response->set_return_code(return_code_e::E_OK);
its_response->set_reliable(_request->is_reliable());
return (its_response);
}
/******************消息的实现类***********************************/
class message_impl
: virtual public message,
virtual public message_base_impl {
public:
VSOMEIP_EXPORT message_impl();
VSOMEIP_EXPORT virtual ~message_impl();
VSOMEIP_EXPORT length_t get_length() const;
VSOMEIP_EXPORT void set_length(length_t _length);
VSOMEIP_EXPORT std::shared_ptr< payload > get_payload() const;
VSOMEIP_EXPORT void set_payload(std::shared_ptr< payload > _payload);
VSOMEIP_EXPORT bool serialize(serializer *_to) const;
VSOMEIP_EXPORT bool deserialize(deserializer *_from);
VSOMEIP_EXPORT uint8_t get_check_result() const;
VSOMEIP_EXPORT void set_check_result(uint8_t _check_result);
VSOMEIP_EXPORT bool is_valid_crc() const;
VSOMEIP_EXPORT uid_t get_uid() const;
VSOMEIP_EXPORT void set_uid(uid_t _uid);
VSOMEIP_EXPORT gid_t get_gid() const;
VSOMEIP_EXPORT void set_gid(gid_t _gid);
protected: // members
std::shared_ptr< payload > payload_; //存放序列化后event消息的payload,该类也是接口类,可以通过vsomeip源码,看到该接口类的实现类是payload_impl
uint8_t check_result_;
uid_t uid_;
gid_t gid_;
};
/******************存放event序列化后的数据***********************************/
class payload_impl: public payload {
public:
VSOMEIP_EXPORT payload_impl();
VSOMEIP_EXPORT payload_impl(const byte_t *_data, uint32_t _size);
VSOMEIP_EXPORT payload_impl(const std::vector< byte_t > &_data);
VSOMEIP_EXPORT payload_impl(const payload_impl& _payload);
VSOMEIP_EXPORT virtual ~payload_impl();
VSOMEIP_EXPORT bool operator == (const payload &_other);
VSOMEIP_EXPORT byte_t * get_data();
VSOMEIP_EXPORT const byte_t * get_data() const;
VSOMEIP_EXPORT length_t get_length() const;
VSOMEIP_EXPORT void set_capacity(length_t _capacity);
VSOMEIP_EXPORT void set_data(const byte_t *_data, length_t _length);
VSOMEIP_EXPORT void set_data(const std::vector< byte_t > &_data);
VSOMEIP_EXPORT void set_data(std::vector< byte_t > &&_data);
VSOMEIP_EXPORT bool serialize(serializer *_to) const;
VSOMEIP_EXPORT bool deserialize(deserializer *_from);
private:
std::vector<byte_t> data_; //存放待发送的数据
};
在vsomeip event通信流中,在event消息发送前,还会做一次序列化。该序列化的主要目的是在payload之前加入event相关的信息,session_id,servce_id以及instance_id等相关的信息