当前位置:   article > 正文

译:vsomeip 10 分钟阅读

vsomeip

译:vsomeip 10 分钟阅读

原文:vsomeip in 10 minutes


SOME/IP 简短介绍

SOME/IP 是 “Scalable service-Oriented MiddlewarE over IP” 的缩写,中文翻译为:基于 IP 的可扩展面向服务中间件。该中间件设计用于典型的汽车用例,并与 AUTOSAR 兼容(至少在有线格式级别上)。公众可访问的规范可从以下站点获取 http://some-ip.com/。在本 wiki 中,我们不想进一步探讨另一个中间件规范的原因,但想大致概述一下 SOME/IP 规范及其开源实现 vsomeip 的基本结构,而不要求完整性。

让我们从 SOME/IP 规范的三个主要部分开始:

  • On-wire format —— 在线格式
  • Protocol —— 协议
  • Service Discovery —— 服务发现

SOME/IP 在线格式

原则上,SOME/IP 通信包括基于 IP 在设备或订阅者之间发送消息,参考以下图片:

someip-on-wire-format

在图中你可以看到两个设备(A 和 B);设备 A 向 B 发送一条 SOME/IP 消息并从设备 B 接收一条信息回来。底层传输协议可以是 TCP 或者 UDP;对于消息本身是没有区别的。现在我们假设设备 B 正在运行一个服务,该服务提供一个由这个消息从设备 A 调用的函数,返回的消息就是答案。

SOME/IP 消息有两部分:headerpayload
在图中你可以看到 header 由以下的标识符组成:

  • Service ID: 每个服务的唯一标识符
  • Method ID: 0-32767 用作 Method,32768-65535 用作 Event
  • Length: payload 的长度字节(还包括下一个 ID,这意味着增加 8 个字节)
  • Client ID: ECU 内部调用客户端的唯一标识符;在整个车辆中必须是唯一的
  • Session ID: 用于会话处理的标识符;每次调用必须递增
  • Protocol Version: 0x01
  • Interface Version: 服务接口的主要版本
  • Message Type:
    • REQUEST (0x00) 期待响应的请求(即使返回 void)
    • REQUEST_NO_RETURN (0x01) fire & forget 请求
    • NOTIFICATION (0x02) 通知/事件回调请求不需要响应
    • RESPONSE (0x80) 响应消息
  • Return Code:
    • E_OK (0x00) 没有发生错误
    • E_NOT_OK (0x01) 发生了未指定的错误
    • E_WRONG_INTERFACE_VERSION (0x08) 接口版本不匹配
    • E_MALFORMED_MESSAGE (0x09) 反序列化错误,因此无法反序列化 payload
    • E_WRONG_MESSAGE_TYPE (0x0A) 收到意外的消息类型(例如,用于 Method 的 REQUEST_NO_RETURN 被定义成了 REQUEST)

我们看到,正常函数调用有“请求”和“响应”,客户端已订阅的事件有通知消息。错误报告为正常响应,通知则带有适当的返回代码。

payload 包含序列化数据。该图显示了在传输的数据结构是仅具有基本数据类型的嵌套结构的简单情况下的序列化。在这种情况下就很简单:结构元素只是平展的,这意味着它们只是一个接一个地写入 payload 中。

SOME/IP 协议

在本节中主要有两点很重要,现将予以说明:

  • 所谓的传输绑定(TCP 与 UDP)。
  • 基本的通讯模式:发布/订阅、请求/响应。

如上所述,底层传输协议可以是 UDP 或 TCP。在 UDP 情况下,SOME/IP 消息不分段;一个 UDP 数据包中可能包含多条消息,但一条消息的长度不能超过 UDP 数据包的长度(最多 1400 字节)。更大的消息必须通过 TCP 传输,在这种情况下,使用 TCP 的所有健壮性特征。如果在 TCP 流中发生同步错误,SOME/IP 规范允许调用所谓的神奇 Cookie 以便再次找到下一条消息的开关。

请注意,服务接口必须实例化,因为同一个接口可能有多个实例,所以必须为实例定义一个附加标识符(实例 ID)。但是实例 ID 不是 SOME/IP 消息头的一部分。实例通过传输协议的端口号进行标识;这意味着不可能在同一个端口上提供同一接口的多个实例。

现在请看下图,其中显示了基本的 SOME/IP 通信模式:

someip-protocol

除了远程过程调用的标准“请求/响应”机制之外,还有事件的“发布/订阅”模式。注意到事件在 SOME/IP 协议中总是分组在事件组中;因此只能订阅事件组而不是订阅事件本身。SOME/IP 规范也定义了“字段”;在这种情况下,setter/getter 方法遵循“请求/响应”模式,更改的通知消息是事件。订阅本身通过 SOME/IP 服务发现完成。

SOME/IP 服务发现

SOME/IP Service Discovery 服务发现用于定位服务实例并检测服务实例是否正在运行,以及实现“发布/订阅”处理。这主要通过所谓的 offer 消息来完成;这意味着每个设备广播(多播)包含该设备提供的所有服务的消息。SOME/IP SD 消息通过 UDP 发送。如果客户端应用程序需要的服务目前尚未提供,则可以发送“查找消息”。其他的 SOME/IP SD 消息可用于发布或订阅事件组。

下图显示了一个 SOME/IP SD 消息的一般结构。

someip-service-discovery

对于入门这应该足够了,更多的细节将在后面的示例中讨论或者可以参阅 SOME/IP SD 规范

vsomeip 简短概述

在开始实现介绍性示例之前,让我们先简单了解一下 SOME/IP 的 GENIVI 实现的基本结构——vsomeip。

vsomeip-overview

如图所示,vsomeip 不仅涵盖设备之间的 SOME/IP 通信(外部通信),还包括内部进程间通信。两个设备通过所谓的通信端点(Endpoint)进行通信,通信端点确定所使用的传输协议(TCP 或 UDP)及其参数作为端口号或其他参数。所有这些参数都是可以在 vsomeip 配置文件(json 文件,请参阅 vsomeip 用户指南)中设置的配置参数。内部通信通过本地端点(Local Endpoint)完成,本地端点由使用 Boost.Asio 库的 unix domain socket 实现。由于这种内部通信不是通过中央组件(例如,像 D-Bus 守护进程)路由的,因此速度非常快。

只有当消息必须发送到外部设备时,中央 vsomeip 路由管理器才会获取消息,并分发来自外部的消息。每个设备只有一个路由管理器;如果未配置任何内容,则第一个运行的 vsomeip 应用程序也会启动路由管理器。

注意:vsomeip 并不实现数据结构的序列化!CommonAPI 的 SOME/IP 绑定涵盖了这一点,vsomeip 仅涵盖了 SOME/IP 协议和服务发现。

这是 SOME/IP 和 vsomeip 非常非常简短的概述。但对于第一次开始这已经足够了;进一步的细节将在示例中进行解释。

准备/先决条件

如前所述,vsomeip 需要 Boost.Asio 库,所以确保你已经在你的系统上安装了 BOOST (最低版本 1.55)。

译者:在 Ubuntu 系统中你可以使用以下指令安装 Boost 开发库:

sudo apt install -y libboost-dev libboost-system-dev \
libboost-thread-dev libboost-filesystem-dev \
libboost-log-dev libsystemd-dev
  • 1
  • 2
  • 3

当 Boost 已经成功安装,你可以像往常一样毫无困难地构建 vsomeip:

cd vsomeip
mkdir build
cd build
cmake ..
make
  • 1
  • 2
  • 3
  • 4
  • 5

这是可行的,但为了避免以后出现一些特殊问题,我建议在 CMake 调用中至少添加一个参数:

cmake -DENABLE_SIGNAL_HANDLING=1 ..
  • 1

此参数确保您可以毫无问题地终止 vsomeip 应用程序(否则当你使用 Ctrl-C 停止应用程序时,可能无法正确删除共享内存段 /dev/shm/vsomeip)。

译者:为了让后续的应用程序能够正常运行,建议将编译好的 vsomeip 库部署到你的 Ubuntu 系统中并更新库的缓存:

sudo make install
sudo ldconfig
  • 1
  • 2

第一个应用程序

创建第一个 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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这非常简单:你必须首先创建一个应用程序对象,然后初始化并启动它。在创建 vsomeip 应用程序后,必须首先调用 init 方法,并执行以下步骤对其进行初始化:

  • 加载配置
  • 确定路由配置和路由初始化
  • 安装信号处理

为了启动消息处理,必须在 init 之后调用 start 方法。接收到的消息通过套接字进行处理,注册的回调用于将它们传递给用户应用程序。

准备好了

现在创建一个用于构建应用程序的 CMake 文件,它可能看起来像这样:

CMakeLists.txt (Example)

cmake_minimum_required (VERSION 2.8)

set (CMAKE_CXX_FLAGS "-g -std=c++11")

find_package (vsomeip3 3.1.20 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-example.cpp)
target_link_libraries(service-example vsomeip3 ${Boost_LIBRARIES})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

照常进行(创建 build 目录,运行 CMake 构建),然后启动程序(service-example),你应该在控制台上获得以下输出(或类似输出):

2022-08-11 10:04:42.254418 [info] Parsed vsomeip configuration in 0ms
2022-08-11 10:04:42.255106 [info] Configuration module loaded.
2022-08-11 10:04:42.255244 [info] Initializing vsomeip application "World".
2022-08-11 10:04:42.255868 [info] Instantiating routing manager [Host].
2022-08-11 10:04:42.256312 [info] create_local_server Routing endpoint at /tmp/vsomeip-0
2022-08-11 10:04:42.256942 [info] Service Discovery enabled. Trying to load module.
2022-08-11 10:04:42.259474 [info] Service Discovery module loaded.
2022-08-11 10:04:42.259952 [info] Application(World, 0100) is initialized (11, 100).
2022-08-11 10:04:42.260123 [info] Starting vsomeip application "World" (0100) using 2 threads I/O nice 255
2022-08-11 10:04:42.261101 [info] main dispatch thread id from application: 0100 (World) is: 7fcab8e85700 TID: 15665
2022-08-11 10:04:42.261164 [info] shutdown thread id from application: 0100 (World) is: 7fcab8684700 TID: 15666
2022-08-11 10:04:42.262063 [info] Watchdog is disabled!
2022-08-11 10:04:42.262699 [info] io thread id from application: 0100 (World) is: 7fcabbae66c0 TID: 15664
2022-08-11 10:04:42.262755 [info] io thread id from application: 0100 (World) is: 7fcab7682700 TID: 15668
2022-08-11 10:04:42.263730 [info] vSomeIP 3.1.20.3 | (default)
2022-08-11 10:04:42.264287 [info] Network interface "lo" state changed: up
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

请注意:

  • 这些步骤对于服务端和客户端是相同的;没有区别。它只是一个 vsomeip 应用程序。
  • 到目前为止,你不需要任何配置文件。

让我们详细讨论一些问题。

  • 首先,你看到配置已经被加载;但你并没有进行配置,因此使用的是默认值。
  • 你没有为应用程序配置客户端 ID;因此,vsomeip 的特性,自动配置会找到适当的客户端 ID,第一个数字是 0x0001
  • 路由管理器同样没有配置;因此,路由管理器自动跟随系统中第一个 vsomeip 应用程序启动,这就是 service-example
  • 服务发现默认情况下是启用的,没有静态路由,这需要一些配置参数。
  • init() 的最后输出是 Application(World, 0100) is initialized (11, 100)。最后的两个数字表示,如果回调阻塞超过 100ms 则 vsomeip 使用的调度器的最大数量为 11。这些参数可以配置。
  • 默认情况下,创建两个线程来接收 SOME/IP 消息;这允许 vsomeip 并行处理长消息。
  • 然后你看到当前的 vsomeip 版本和 SOME/IP 路由已经准备就绪。

可用性

到目前为止,应用程序并没有做太多的工作,客户端与服务端之间没有区别。现在假设我们的 service-example 是服务端,我们想要写一个希望使用该服务的客户端。在第一步中,我们必须触发应用程序以提供服务实例,这可以通过在第一个示例中添加 offer_service 命令来实现:

服务示例 service-example.cpp

#include <vsomeip/vsomeip.hpp>

#define SAMPLE_SERVICE_ID 0x1234
#define SAMPLE_INSTANCE_ID 0x5678

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

int main() {

    app = vsomeip::runtime::get()->create_application("World");
    app->init();
    app->offer_service(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID);
    app->start();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在下一步,我们编写一个应用程序检查正在运行 “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_availability(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.")  << std::endl;
}

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

为了尽可能简单,我们省略了所有可能的检查,例如注册是否成功。作为客户端,你必须告诉 vsomeip 你想要使用该服务,并且你需要注册回调,以便在服务可用时获得调用。客户端输出现在应该类似于:

Service [1234.5678] is NOT available.
2022-08-11 17:33:32.691125 [info] ON_AVAILABLE(0101): [1234.5678:0.0]
Service [1234.5678] is available.
  • 1
  • 2
  • 3

app->start() 启动 vsomeip 事件循环时将调用可用性回调。

在服务侧应该有以下额外的输出:

2022-08-11 17:33:26.374865 [info] OFFER(0100): [1234.5678:0.0] (true)
  • 1

请求/响应

从一个通用的 vsomeip 应用程序开始,我们创建了一个服务,该服务提供服务接口的实例,和一个希望使用该接口的客户端。下一步是在服务端实现一个可由客户端调用的函数。

服务示例必须准备好接收信息;这可以通过注册消息处理函数来完成。请查看以下代码:

带有服务提供和消息处理的 service-example.cpp

#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);
}

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

在客户端它有点复杂:

带有消息处理和发送函数的 client-example.cpp

#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);
}

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

与服务端一样,我们需要注册一个消息处理函数来接收调用的响应。原则上创建发送消息是非常容易的(请求)。只需通过调用 create_request() 来获取请求对象,设置服务 ID、实例 ID 和方法 ID,最后将你的序列化数据写入 payload。在这里的示例中,我们将 0 到 9 的值写入 payload (std::vector< vsomeip::byte_t >)。

当我们试图从客户端向服务端发送请求时,我们遇到了一个小问题。必须先启动应用程序(app->start())然后才能发送消息,因为我们需要一个正在运行的事件循环来处理消息。但是 app->start() 方法不返回,因为它内部有运行事件循环。所以,在调用 app->send(request, true) 之前,我们启动一个线程(run)并在此线程中等待可用性回调的返回。

现在你应该得到的输出(我首先启动了服务):

2022-08-13 11:41:17.817276 [info] Client [100] is connecting to [101] at /tmp/vsomeip-101
2022-08-13 11:41:17.818534 [info] REGISTERED_ACK(0101)
SERVICE: Received message with Client/Session [0101/0001] 00 01 02 03 04 05 06 07 08 09 
2022-08-13 11:41:17.913784 [info] REQUEST(0101): [1234.5678:255.4294967295]
CLIENT: Received message with Client/Session [0101/0001] 09 08 07 06 05 04 03 02 01 00 
CLIENT: Service [1234.5678] is available.
2022-08-13 11:41:17.915125 [info] ON_AVAILABLE(0101): [1234.5678:0.0]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

订阅/通知

到目前为止,我们已经创建了一个实现方法的服务和一个调用该方法的客户端。但这并不是所有的可能,SOME/IP 规范还描述了事件处理。这意味着应用程序可以发送事件,如是订阅者感兴趣,他们可以订阅这些事件。通过定义 setter 和 getter 方法,可以实现提供属性的服务。为了不太复杂,我们在示例中删除了方法调用的实现,并实现了事件处理。首先让我们看看服务。

译者:在文件头部定义 EventID 及 EventGroupID 的值:

#define SAMPLE_EVENT_ID 0x8001
#define SAMPLE_EVENTGROUP_ID 0x0001
  • 1
  • 2

请在主函数中添加以下行:

const vsomeip::byte_t its_data[] = { 0x10 };
payload = vsomeip::runtime::get()->create_payload();
payload->set_data(its_data, sizeof(its_data));

std::set<vsomeip::eventgroup_t> its_groups;
its_groups.insert(SAMPLE_EVENTGROUP_ID);
app->offer_event(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, SAMPLE_EVENT_ID, its_groups);
app->notify(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, SAMPLE_EVENT_ID, payload);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

请注意:

  • 你必须提供该事件,以便向 World 的其他人宣布该事件的存在。
  • 使用 notify 方法,你可以将事件发送给任何已订阅的人。
  • 每个事件都属于一个事件组!但它也可以属于多个事件组。
  • 事件不独立于服务而存在;如果尚未提供服务,则该服务对客户端不可用,客户端无法订阅。

在客户端实现以下功能(为了更好地理解,我省略了前面讨论的所有内容):

...

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

  std::set<vsomeip::eventgroup_t> its_groups;
  its_groups.insert(SAMPLE_EVENTGROUP_ID);
  app->request_event(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, SAMPLE_EVENT_ID, its_groups);
  app->subscribe(SAMPLE_SERVICE_ID, SAMPLE_INSTANCE_ID, SAMPLE_EVENTGROUP_ID);

}

void on_message(const std::shared_ptr<vsomeip::message> &_response) {
    std::stringstream its_message;
    its_message << "CLIENT: received a notification for event ["
            << std::setw(4) << std::setfill('0') << std::hex
            << _response->get_service() << "."
            << std::setw(4) << std::setfill('0') << std::hex
            << _response->get_instance() << "."
            << std::setw(4) << std::setfill('0') << std::hex
            << _response->get_method() << "] to Client/Session ["
            << std::setw(4) << std::setfill('0') << std::hex
            << _response->get_client() << "/"
            << std::setw(4) << std::setfill('0') << std::hex
            << _response->get_session()
            << "] = ";
    std::shared_ptr<vsomeip::payload> its_payload = _response->get_payload();
    its_message << "(" << std::dec << its_payload->get_length() << ") ";
    for (uint32_t i = 0; i < its_payload->get_length(); ++i)
        its_message << std::hex << std::setw(2) << std::setfill('0')
            << (int) its_payload->get_data()[i] << " ";
    std::cout << its_message.str() << std::endl;
}

...

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(vsomeip::ANY_SERVICE, vsomeip::ANY_INSTANCE, vsomeip::ANY_METHOD, on_message);

    std::thread sender(run);
    app->start();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

你可以看到,实现没有任何困难:

  • 同样,你需要事件组来订阅事件。
  • 你必须先请求事件,然后才能订阅。
  • 要接收事件,只需注册一个标准消息处理函数;在这种情况下,你可以使用非常方便的通配符。

在控制台中,你现在应该看到以下的行输出:

2022-08-14 11:46:43.086133 [info] REGISTER EVENT(0100): [1234.5678.8001:is_provider=true]
2022-08-14 11:46:51.983577 [info] REGISTER EVENT(0101): [1234.5678.8001:is_provider=0:reliability=ff]
...
2022-08-14 11:46:51.079410 [info] SUBSCRIBE(0101): [1234.5678.0001:ffff:0]
2022-08-14 11:46:51.080129 [info] SUBSCRIBE ACK(0100): [1234.5678.0001.ffff]
  • 1
  • 2
  • 3
  • 4
  • 5

圆括号中的数字同样是客户端 ID;我首先启动了服务,因此服务从自动配置中得到了 0100 号,客户端得到了 0101 号。

两个设备间通讯

SOME/IP 尚未发明用于一个设备内的进程间通信(如 D-Bus),但已发明用于多个设备之间基于 IP 的通信。如果你想使用迄今为止为两个设备之间的通信开发的示例,无需更改 C++ 代码;但是你必须编写 vsomeip 配置文件。有关详细信息请参阅 vsomeip 用户指南;在这里,我们只讨论让系统运行的要点。

首先,我将省略一些关于 vsomeip 配置的介绍性文字。

  • 堆栈由一个或多个 json 格式的文件配置(http://www.json.org/)。
  • json 文件的标准文件夹是 /etc/vsomeip
  • 还可以通过设置环境变量 VSOMEIP_CONFIGURATION 来更改此文件夹或定义单个配置文件。
  • 也可以将配置文件复制到包含可执行应用程序(本地配置)的文件夹中。

对于下面的配置示例,我假设服务在地址为 172.17.0.2 的设备上运行,而客户端的地址为 172.17.0.1

首先,让我们看一下服务配置的示例。

{
    "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"
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

对于通过 IP 的通信,单播(unicast)地址是强制的。让我们讨论其他条目:

  • logging: 这些设置是可选的;设置 “console”=true 以查看控制台上的日志信息。
  • applications: 你可以为每个应用程序(你通过 create_application(<name>) 创建的)定义一个固定的客户端 ID,而不是由自动配置来确定。这将帮助你以后在跟踪中识别应用程序。这里必须设置客户端 ID,因为客户端 ID 在你的网络中必须是唯一的。如果不设置 clientID,自动配置将在每个设备上计算 clientID 1,通信将无法工作。
  • services: 对于每个服务实例,必须定义在哪个端口下可以访问它。如果端口是 “unreliable” 则为 UDP 端口;如果是 "reliable“,则传输层是 TCP。
  • routing: 每个设备只有一个路由管理器。此路由管理器将连接到启动的第一个 vsomeip 应用程序或此处定义的应用程序。
  • service-discovery: 所有这些参数只有在启用服务发现时才有意义。在这种情况下,强制参数是用于发送服务发现消息的多播地址以及端口和协议。其他参数确定发送 offer 消息的频率、延迟等。请查看用户指南或 SOME/IP 规范。

注意: 确保你的设备已配置为接收多播消息(例如,通过 route add -nv 224.224.224.245 dev eth0 或类似;这取决于以太网设备的名称)。

考虑客户端的以下配置:

{
    "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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

由于客户端不提供服务,因此不需要 “services” 设置。

欢迎关注我的公众号:飞翔的小黄鸭
也许会发现不一样的风景


△ \triangle 译:SOME/IP 技术细节

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/136745
推荐阅读
相关标签
  

闽ICP备14008679号