vSOMEIP探究

工作需要接触以下vSOMEIP,这篇文章记录在探索过程中的经验
github上的vSOMEIP项目:https://github.com/GENIVI/vsomeip

名词解释

boost:一个可移植的C++库

项目READEME

vsomeip Overview

协议栈包含如下的的的东西

  • 一个SOME/IP的共享库(libvsomeip3.so)
  • 一个用于SOME/IP的服务发现库(libvsomeip3-sd.so),可以在运行时加载,如果使能了服务发现

vsomeip in 10 minutes

SOME/IP 介绍

SOME/IP是”Scalable service-Oriented middlewarE over IP“,这个中间件用用来为汽车设计的,他是兼容AUTOSAR的,相关的公开的介绍在http://some-ip.com/中,这个wiki不会深入解释其他中间件指定的原因,而是对SOME/IP做了整体的基本介绍和它的开元实现
下面是SOME/IP定义的三个主要的部分

  • On-wire 格式
  • 协议
  • 服务发现

SOME/IP On-wire Format

原则上来说,SOME/IP通信由设备间或通过IP之上的订阅者之间发送的消息构成,看下面的图片
在这里插入图片描述这里有两个设备(A和B),A设备发送一个SOME/IP报文给B ,并且得到一个返回报文,潜在的通信协议可以是TCP或UDP,对于消息本身是没有差别的,现在我们假设设备B正在运行一个服务,A可以通过这条Message调用这个服务,这个服务返回一条A发出的Message的响应
SOME/IP Message由两部分组成:头和数据段,上图中可以看到报文头中有如下主要的标识:

  • Service ID:每个服务都有唯一的标识符
  • Method ID:0 - 32767表示Method,32768 - 65535表示events
  • Length:数据段的长度(包含下面的IDs,也就是多8个字节)
  • Client ID:ECU内部的client的唯一的标识符,整车上是唯一的
  • Session ID:会话的标识符,每次调用都会增加
  • Protocol Version:0x01
  • Interface Version:服务接口的主要版本
  • Message Type:
    • REQUEST(0x00):期待响应的请求
    • REQUEST_NO_RETURN(0x01):无需响应的请求
    • NOTIFICATION(0x02):一个notification/event的请求,不期待响应
    • RESPONSE(0x80):响应报文
  • Return Code:
    • E_OK(0x00):没有错误发生
    • E_NOT_OK(0x01):一个未定义的错误发生
    • E_WRONG_INTERFACE_VERSION(ox08):接口版不匹配
    • E_MALFORMED_MESSAGE(0x09):序列化失败
    • E_WRONG_MESSAGE_TYPE(0x0A):受到了为定义的消息类型

我们看到有“REQUESTs”和“RESPONSEs”常规请求和客户端订阅的event的notification message,错误是常规的响应,不同的错误有不同的返回码
数据场包含序列化的数据,这张图展示了序列化的简单的形式

SOME/IP 协议

主要有2点比较重要:

  • so-called传输绑定(UDP和TCP)
  • 基本的传输模式:发布/订阅和请求/响应

上面说到了潜在的传输协议可以是UDP或者TCP,UDP的情况下,SOME/IP Message 不是片段的,可以有超过一个Messsage在一个UDP报文中,但是,不能呢个超过UDP 报文的长度(最高是1400Bytes),更大的message必须通过TCP传输,在这种情况下可以用到TCP的所有鲁棒的特性,如果在TCP中有传输故障出现,SOME/IP规范允许so-call magic cookies用来再次寻找下一条message的起始
需要注意的是服务接口必须是被实例化,因为可能有很多实例都有相同的接口,而且每个实例定义都有一个附加的标识符(instance ID),然而 instance ID不是报文头的一部分,instance ID是通过port口号来定义的,这意味着同一个接口的不同实例不可能有相同的port
下图展示了基本的SOME/IP通信模式
在这里插入图片描述
除了标准的REQUEST/RESPONSE机制可以被远程调用,还有PUBLISH/SUBSCRIBE的事件机制,需要注意的是在SOME/IP协议的event通常是在同一个group中的,因此只有可能订阅整个event group而不是订阅单一的event本身,SOME/IP规范也称“fields”,这种情况下setter/getter方法遵循R/R模式,并且通知message是周期发送的,订阅本身是通过SOME/IP的服务发现来完成的。

SOME/IP Service discovery

SOME/IP SD是给用在本地的服务实例上的,用来监测是否服务实例是否实现了发布/订阅的机制,主要通过so-called提供message来实现的,这意味着每个设备会广播message,message中会包含所有需要提供给该设备的服务,SOME/IP message是通过UDP来发送message的,如果服务被client应用所请求,但是这个时候没有提供,然后“find messages”可以被发送,其他的SOME/IP SD message可以在一个eventgoup中被发布或者订阅
下图展示了SOME/IP message 的整体结构
在这里插入图片描述
这些对于初学者涞水足够了,详细的可以在示例中看到

vsomeip short overview

在开始执行介绍的例子之前,先简单看看GENIVI实现的基本的结构
在这里插入图片描述

如图片中所显示的,SOME/IP通信不仅仅是设备间的通信(外部通信)还包括内部通信,两个设备通过Endpoints来通信(使用TCP或者UDP)还有端口号和其他的参数,所有这些参数通过vsomeip的配置文件来设置(json格式,看vsomeip的用户手册),内部的通信通过内部的socket来通信,使用Boost.Asio库,尽管内部通信不通过中央组件(比如D-Bus),它依然是很快的。
中央vsomeip组件管理器只有在受到外部设备发送的message后,然后它把message分散出去,每一个设备上只有一个组建管理器,如果第一次运行someip的时候什么也没有配置,它仍然会开启
vsomeip不会实现数据结构的序列化,这些是由SOME/IP绑定的的API来实现的,vsomeip仅仅包含SOME/IP协议和服务发现
这只是SOME/IP的一个非常短的介绍,但是对于一开始也够了

Preparation / Prerequisites

之前提到了vsomeip是需要boost.Asio库,所以确定年你已经装了BOOST的库,当boost成功的装好了以后,你可以像往常一样轻松的构建vsomeip

cd vsomeip
mkdir build
cmake ..
make

这会工作,但是如果想避免一些特别的问题,我建议在你的CMake文件中至少加上下面一个参数

/build$ cmake -DENABLE_SIGNAL_HANDLING=1 ..

这个参数确保你可以关闭你的vsomeip应用程序而且不会有问题(除非共享库内存空间 /dev/shm/vsomeip 在使用 Ctrl-C 停止应用程序的三时候没有正确的移除)

First Application

创建一个vsomeip应用,我们叫他 service-example
service-example.cpp

#include <vsomeip/vsomeip.hpp>

std::shared_ptr< vsomeip::application > app;

int main() {
	app = vsomeip::runtime::get()->create_application("World");
	app->init();
	app->start();
}

这很简单,你已经创建了第一个应用对象,初始化它而且启动了他,init方法一定要在创建vsomeip应用之前调用,会执行下面的步骤来初始化它:

  • 加载配置
  • 检测路由配置并且初始化路由
  • 安装信号句柄

开始方法在调用init之后执行用来开始message进程,接收message将会通过sockets和寄存器回调函数,用他们给用户应用

READY

现在创建一个CMake文件来构建这个应用,它看起来像这个样子
CMakeLists.txt (Example)

cmake_minimum_required (VERSION 2.8)

set (CMAKE_CXX_FLAGS "-g -std=c++0x")

find_package (vsomeip 2.6.0 REQUIRED)
find_package (BOOST 1.55 COMPONENTS system thread log REQUIRED)

include_directories (
	${Boost_INCLUDE_DIR}
	${VSOMEIP_INCLUDE_DIRS}
)

add_executable(service-example ../src/service-sample.cpp)
target_link_libraries(service-example vsomeip ${Boost_LIBRARIES})

像往常一样继续(创建build路径,运行CMake并且build),然后开始它 (service-example),你应该会在console中得到输出

2017-03-20 10:38:20.885390 [info] Parsed vsomeip configuration in 0ms
2017-03-20 10:38:20.889637 [info] Default configuration module loaded.
2017-03-20 10:38:20.889797 [info] Initializing vsomeip application "World".
2017-03-20 10:38:20.890120 [info] SOME/IP client identifier configured. Using 0001 (was: 0000)
2017-03-20 10:38:20.890259 [info] No routing manager configured. Using auto-configuration.
2017-03-20 10:38:20.890367 [info] Instantiating routing manager [Host].
2017-03-20 10:38:20.890641 [info] init_routing_endpoint Routing endpoint at /tmp/vsomeip-0
2017-03-20 10:38:20.890894 [info] Client [1] is connecting to [0] at /tmp/vsomeip-0
2017-03-20 10:38:20.891039 [info] Service Discovery enabled. Trying to load module.
2017-03-20 10:38:20.891647 [info] Service Discovery module loaded.
2017-03-20 10:38:20.892045 [info] Application(World, 1001) is initialized (11, 100).
2017-03-20 10:38:20.892210 [info] Starting vsomeip application "World" using 2 threads
2017-03-20 10:38:20.892668 [info] Watchdog is disabled!
2017-03-20 10:38:20.893312 [info] Network interface "lo" is up and running.
2017-03-20 10:38:20.898471 [info] vSomeIP 2.6.2
2017-03-20 10:38:20.898708 [info] Sent READY to systemd watchdog
2017-03-20 10:38:20.898854 [info] SOME/IP routing ready.

需要注意的是:

  • 这些步骤很类似于服务器和客户端,他们没有什么区别,仅仅是一个vsomeip应用
  • 直到现在你不需要任何的配置文件

我们来说说一些细节的地方

  • 首先你看到的配置文件已经被加载了,你不需要配置,因此你使用的是默认的配置
  • 你不需要为你的应用配置一个client ID,因此这是一个vsomeip的特性,自动配置,寻找一个应用的client ID,第一个数字是 0x001
  • 也不路由器也不需要配置,因此路由器是在vsomeip应用第一次启动的时候自动运行的,这个就是service-example
  • 默认情况下服务发现是启用的,没有静态路由,这可能需要一些配置参数
  • 最后的init()的输出是Application(world, 1) is initialized (11, 100)。这两个最后的数的意思是如果回调模块超过了100ms的话,最大的调度程序vsomip使的使用是11,这个参数是可以被配置的
  • 默认情况下两个线程会创建,用来接收SOME/IP message,这允许vsomeip并行的操作长message
  • 然后你会看到当前的vsomeip版本然后SOME/IP路由已经ready

Availability

到目前为止这个应用不会做更多的工作了,然后这和client和service没有差别,现在我们假设我们的 service-example 是service然后我们想去写一个client然后使用这个服务,第一步是我们出发这个应用来提供一个service实例,这些可以通过在我们的示例中添加一个offer_service命令

service-example.cpp with offer

#include <vsomeip/vsomeip.hpp>

#define SAMPLE_SERVICE_ID 0x1234
#define SAMPLE_INSTANCE_ID 0x5678

std::shard_ptr< vsomeip::application > app

int main() {
	app = vsompip::runtime::get()->create_application("World");
	app->init();
	app->offer_service(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID);
	app->start();
}

下一步我们写一个应用来检查是运行着的“World”应用是可用的,考虑下面的client-example代码,创建了一个名为Hello 的应用程序

client-example.cpp

#include <iomanip>
#include <iostream>

#include <vsomeip/vsomeip.hpp>

#define SAMPLE_SERVICE_ID 0x1234
#define SAMPLE_INSTANCE_ID 0x5678

std::shared_ptr< vsomeip::application > app;

void on_availablility(vsomeip::service_t _service, vsomeip::instance_t _instance, bool _is_available) {
	std::cout << "Service ["
		<< std::setw(4) << std::setfill('0') << std::hex << _service << "." << _instance
		<< "] is " << (_is_available ? "available." : " NOT available.")
}

int main() {
    app = vsomeip::runtime::get()->create_application("Hello");
    app->init();
    app->register_availability_handler(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, on_availability);
    app->request_service(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID);
    app->start();
}

为了保持尽可能的简单,我们省略了所有的必要的检查,比如注册是否成功,作为client,你需要告诉告诉vsomeip你向使用这个服务并且你需要注册一个回调函数,为了在服务可用的时候得到一个对话,client的输出类似这样

Service [1234.5678] is NOT available.
2017-03-21 04:14:37.720313 [info] REQUEST(0002): [1234.5678:255.4294967295]
Service [1234.5678] is available.

当vsomeip事件循环通过app->start()开始的时候,可用的回调被调用
在service端会有下面的行

2017-03-21 04:14:33.850964 [info] OFFER(0001): [1234.5678:0.0]

Requset/Responce

从一个普通的vsomeip应用程序开始,我们创建了一个service,这个service提供了一个service接口的实例,然后一个client想要使用这个接口,现在下一步是在service端实现一个函数,这个函数可以被client调用
这个service例子是用来准备接收一个message,可以通过接收一个message句柄,请看下面的代码
service-example.cpp 和message句柄

#include <iomanip>
#include <iostream>
#include <sstream>

#include <vsomeip/vsomeip.hpp>

#define SAMPLE_SERVICE_ID 0x1234
#define SAMPLE_INSTANCE_ID 0x5678
#define SAMPLE_METHOD_ID 0x0421

std::shared_ptr<vsomeip::application> app;

void on_message(const std::shared_ptr<vsomeip::message> &_request) {

    std::shared_ptr<vsomeip::payload> its_payload = _request->get_payload();
    vsomeip::length_t l = its_payload->get_length();

    // Get payload
    std::stringstream ss;
    for (vsomeip::length_t i=0; i<l; i++) {
       ss << std::setw(2) << std::setfill('0') << std::hex
          << (int)*(its_payload->get_data()+i) << " ";
    }

    std::cout << "SERVICE: Received message with Client/Session ["
        << std::setw(4) << std::setfill('0') << std::hex << _request->get_client() << "/"
        << std::setw(4) << std::setfill('0') << std::hex << _request->get_session() << "] "
        << ss.str() << std::endl;

    // Create response
    std::shared_ptr<vsomeip::message> its_response = vsomeip::runtime::get()->create_response(_request);
    its_payload = vsomeip::runtime::get()->create_payload();
    std::vector<vsomeip::byte_t> its_payload_data;
    for (int i=9; i>=0; i--) {
        its_payload_data.push_back(i % 256);
    }
    its_payload->set_data(its_payload_data);
    its_response->set_payload(its_payload);
    app->send(its_response, true);
}

int main() {

   app = vsomeip::runtime::get()->create_application("World");
   app->init();
   app->register_message_handler(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, SAMPLE_METHOD_ID, on_message);
   app->offer_service(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID);
   app->start();
}

client端会比较复杂一点

#include <iomanip>
#include <iostream>
#include <sstream>

#include <condition_variable>
#include <thread>

#include <vsomeip/vsomeip.hpp>

#define SAMPLE_SERVICE_ID 0x1234
#define SAMPLE_INSTANCE_ID 0x5678
#define SAMPLE_METHOD_ID 0x0421

std::shared_ptr< vsomeip::application > app;
std::mutex mutex;
std::condition_variable condition;

void run() {
  std::unique_lock<std::mutex> its_lock(mutex);
  condition.wait(its_lock);

  std::shared_ptr< vsomeip::message > request;
  request = vsomeip::runtime::get()->create_request();
  request->set_service(SAMPLE_SERVICE_ID);
  request->set_instance(SAMPLE_INSTANCE_ID);
  request->set_method(SAMPLE_METHOD_ID);

  std::shared_ptr< vsomeip::payload > its_payload = vsomeip::runtime::get()->create_payload();
  std::vector< vsomeip::byte_t > its_payload_data;
  for (vsomeip::byte_t i=0; i<10; i++) {
      its_payload_data.push_back(i % 256);
  }
  its_payload->set_data(its_payload_data);
  request->set_payload(its_payload);
  app->send(request, true);
}

void on_message(const std::shared_ptr<vsomeip::message> &_response) {

  std::shared_ptr<vsomeip::payload> its_payload = _response->get_payload();
  vsomeip::length_t l = its_payload->get_length();

  // Get payload
  std::stringstream ss;
  for (vsomeip::length_t i=0; i<l; i++) {
     ss << std::setw(2) << std::setfill('0') << std::hex
        << (int)*(its_payload->get_data()+i) << " ";
  }

  std::cout << "CLIENT: Received message with Client/Session ["
      << std::setw(4) << std::setfill('0') << std::hex << _response->get_client() << "/"
      << std::setw(4) << std::setfill('0') << std::hex << _response->get_session() << "] "
      << ss.str() << std::endl;
}

void on_availability(vsomeip::service_t _service, vsomeip::instance_t _instance, bool _is_available) {
    std::cout << "CLIENT: Service ["
            << std::setw(4) << std::setfill('0') << std::hex << _service << "." << _instance
            << "] is "
            << (_is_available ? "available." : "NOT available.")
            << std::endl;
    condition.notify_one();
}

int main() {

    app = vsomeip::runtime::get()->create_application("Hello");
    app->init();
    app->register_availability_handler(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, on_availability);
    app->request_service(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID);
    app->register_message_handler(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, SAMPLE_METHOD_ID, on_message);
    std::thread sender(run);
    app->start();
}

Subscribe / Notify

Communication between two devices

SOME/IP不是为内部通信设计的,而是在多个设备之间的基于IP的通信,如果你想使用这个例子使用C++开发两个设备之间的通信,可以不用修改这些C++ 代码,只需要修改下面的vsopeip配置文件,详细的请看vsomeip的使用手册,这里仅仅讨论在系统运行过程中最主要的部分
我将首先忽略关于vsomeip配置的整体介绍文字

  • 这个协议栈是由多个json文件来配置的
  • 标准的json文件是 /etc/vsomeip
  • 有可能通过设置环境变量VSOMEIP_CONFIGURATION来改变这个路径或者定义一个单独的配置文件
  • 有可能需要复制配置文件到可执行文件的目录中

详细的配置例子我假设service运行的设备地址是172.17.0.2,client是172.17.0.1
我们先看service的配置文件

{
    "unicast" : "172.17.0.2",
    "logging" :
    { 
        "level" : "debug",
        "console" : "true",
        "file" : { "enable" : "false", "path" : "/tmp/vsomeip.log" },
        "dlt" : "false"
    },
    "applications" : 
    [
        {
            "name" : "World",
            "id" : "0x1212"
        }
    ],
    "services" :
    [
        {
            "service" : "0x1234",
            "instance" : "0x5678",
            "unreliable" : "30509"
        }
    ],
    "routing" : "World",
    "service-discovery" :
    {
        "enable" : "true",
        "multicast" : "224.224.224.245",
        "port" : "30490",
        "protocol" : "udp",
        "initial_delay_min" : "10",
        "initial_delay_max" : "100",
        "repetitions_base_delay" : "200",
        "repetitions_max" : "3",
        "ttl" : "3",
        "cyclic_offer_delay" : "2000",
        "request_response_delay" : "1500"
    }
}

对于基于IP的通信,unicast addrest是强制必须的,我们来看看其他的的部分

  • logging:这个设置是可选的,设置"console"=true来使log message在console
  • applications:你可以为每一个应用定义(你通过create_application(< name >)),一个不定的client ID替代它,它是由autoconfiguration来确定的,它将在后面帮助你在后面的跟踪中识别你的应用程序,需要强制性的设置client ID,因为client ID在你的网络中必须是独一无二的,如果你没有设置client ID,自动配置文件将会在每个设备上计算client ID 1,并且通讯也不会正确运行
  • service:对于每个service实例,其端口号必须是可以使用的,如果这个端口号是“unreliable”的,这可以是一个UDP端口,如果是一个"reliable",那么将会使用TCP来传输
  • routing:每一个设备只有一个路由管理器,这个路由管理器将会被附在第一个vsomeip应用程序上
  • service-discovery:所有的参数仅仅在service-disconvery是能的时候才有意义,在这种情况下强制的参数是广播地址,用来发送服务发现报文,端口号还有协议,另一个参数确定了这个message被发送的频率,延时参数等,请看SOME/IP的用户手册
  • 确保你的设备已经配置了广播地质(224.224.224.245 dev eth0 或者类似的,这取决于你的eth设备的名称)

下面看client端

{
    "unicast" : "172.17.0.1",
    "logging" :
    {
        "level" : "debug",
        "console" : "true",
        "file" : { "enable" : "false", "path" : "/var/log/vsomeip.log" },
        "dlt" : "false"
    },
    "applications" : 
    [
        {
            "name" : "Hello",
            "id" : "0x1313"
        } 
    ],
    "routing" : "Hello",
    "service-discovery" :
    {
        "enable" : "true",
        "multicast" : "224.224.224.245",
        "port" : "30490",
        "protocol" : "udp",
        "initial_delay_min" : "10",
        "initial_delay_max" : "100",
        "repetitions_base_delay" : "200",
        "repetitions_max" : "3",
        "ttl" : "3",
        "cyclic_offer_delay" : "2000",
        "request_response_delay" : "1500"
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值