(二)车载以太网通信中间件 -- 使用vsomeip实现双机通讯

**
**
**
**
**

文中资源:vsomeip+commonapi+指导文档与demo源码

**

前言

上一篇文章介绍了SOME/IP协议的报文格式,本片文章主要来介绍SOME/IP协议的具体实现,即vsomeip协议栈。

vsomeip由GENIVI组织根据SOME/IP协议标准实现的协议栈,如果说SOME/IP协议是一个人的灵魂,那么vsomeip就是受灵魂指导的肉体。本文将从如下几点去展开本文,手把手教你如何使用vsomeip


一、开发环境准备

本文demo主要是基于Android 9系统运行两个linux进程来实现通信,其中vsomeip协议栈的编译也是放在windows环境的Android Studio中来完成,主要编译依赖的环境如下

Windows11
Android Studio
Gradle版本:7.3.3
cmake版本:3.18.1

新建Android Studio工程

使用Android Studio新建一个工程,工程类别选择native c++, 编译链选择default, 新建完成后,在工程根目录新建cmakeexternal两个文件夹备用,cmake主要用来存放编译时库查找的脚本,external主要用来存放协议栈与依赖的动态库代码,此时工程目录如下所示
在这里插入图片描述

下载库源码

boost-cmake
boost 1.71.0
vsomeip协议栈

这里要说一下,vsomeip的网络通信是基于boost库实现的,因此这里咱们也需要将boost中的一个模块编译成静态库集成

下载完成后,将包解压,拷贝到external目录下,如下图所示。
在这里插入图片描述

编写cmake依赖

在cmake目录下按照cmake的规则文档编写FindBoost.cmakeFindvsomeip3.cmake两个文件,以便使用find_package函数在其他的工程中查找包。

同样在工程的根目录下,创建一个CMakeLists.txt文件,填写如下内容:

cmake_minimum_required(VERSION 3.10)

project(SOMEIP)
#把cmake目录下的cmake文件纳入编译链,这样后面能够通过find_package找到对应的库的资源
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
#设置so库的输出路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output/${ANDROID_ABI})
#设置可执行程序的输出路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output/${ANDROID_ABI})
#告诉boost-cmake直接从这个路径去找boost源码
set(FETCHCONTENT_SOURCE_DIR_BOOST ${CMAKE_CURRENT_SOURCE_DIR}/external/boost_1_71_0)
#编译boost
add_subdirectory(external/boost-cmake)
#编译vsomeip
add_subdirectory(external/vsomeip)
#编译demo工程
add_subdirectory(app/src/main/cpp)

此时目录结构如下:
在这里插入图片描述
cmake全部完成后,这里就需要告诉gradle哪个是cmake的入口文件,此时需要修改
app/build.gradle 这个文件,在外围的externalNativeBuild标签中修改如下:

   compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    ...
    ...
    externalNativeBuild {
        cmake {
            path file('../CMakeLists.txt')
            version '3.18.1'
        }
    }

此时我们启动编译,发现还会有一些错误导致无法编译通过,这时候我们需要修改一下external/vsomeip/CMakeLists.txt中的一些内容,解决库依赖以及一些宏定义导致的问题,


二、配置vsomeip编译环境

修改根目录CMakeLists.txt

...
...
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
    set(DL_LIBRARY "")
    set(EXPORTSYMBOLS "")
    set(NO_DEPRECATED "-Wno-deprecated")
    set(OPTIMIZE "")
    set(OS_CXX_FLAGS "-pthread")
endif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")

+if (${CMAKE_SYSTEM_NAME} MATCHES "Android")
+    set(OS "ANDROID")
+    set(DL_LIBRARY "")
+    set(EXPORTSYMBOLS "")
+    set(NO_DEPRECATED "")
+    set(OPTIMIZE "")
+    find_library(ANDROID_LOG_LIB log)
+    set(OS_LIBS ${ANDROID_LOG_LIB})
+    set(OS_CXX_FLAGS "-Wformat-security")
+endif(${CMAKE_SYSTEM_NAME} MATCHES "Android")

#注释掉这段判断,直接定义为支持多路由管理
# Multiple routing managers
#if (ENABLE_MULTIPLE_ROUTING_MANAGERS)
#set (VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS 1)
#else ()
#set (VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS 0)
#endif ()
+ set (VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS 1)

#这里加一下安卓的判断
if(NOT SystemD_FOUND OR ${CMAKE_SYSTEM_NAME} MATCHES "Android")
MESSAGE( STATUS "Systemd was not found, watchdog disabled!")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWITHOUT_SYSTEMD")
else()
list(APPEND OS_LIBS ${SystemD_LIBRARIES})
endif(NOT SystemD_FOUND OR ${CMAKE_SYSTEM_NAME} MATCHES "Android")

#这里也需要加一下安卓环境的判断
if (MSVC)
    message("using MSVC Compiler")
    # add_definitions(-DVSOMEIP_DLL_COMPILATION) now it is controlled per target
    SET(BOOST_WINDOWS_VERSION "0x600" CACHE STRING "Set the same Version as the Version with which Boost was built, otherwise there will be errors. (normaly 0x600 is for Windows 7 and 0x501 is for Windows XP)")
    # Disable warning C4250 since it warns that the compiler is correctly following the C++ Standard. It's a "We-Are-Doing-Things-By-The-Book" notice, not a real warning.
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS -D_WINSOCK_DEPRECATED_NO_WARNINGS -D_WIN32_WINNT=${BOOST_WINDOWS_VERSION} -DWIN32 -DBOOST_ASIO_DISABLE_IOCP /EHsc /std:c++14 /wd4250")
    set(USE_RT "")
    link_directories(${Boost_LIBRARY_DIR_DEBUG})
    ADD_DEFINITIONS( -DBOOST_ALL_DYN_LINK )
else()
    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D${OS} ${OS_CXX_FLAGS} -g ${OPTIMIZE} -std=c++14 ${NO_DEPRECATED} ${EXPORTSYMBOLS}")
    if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android")
        set(USE_RT "rt")
    endif()
endif()

# 所有的库添加对Android Log库的依赖,${OS_LIBS}变量就是
target_link_libraries(${VSOMEIP_NAME}-cfg ${VSOMEIP_NAME} ${Boost_LIBRARIES} ${USE_RT} ${DL_LIBRARY} ${SystemD_LIBRARIES} ${OS_LIBS})
target_link_libraries(${VSOMEIP_NAME} PRIVATE ${Boost_LIBRARIES} ${USE_RT} ${DL_LIBRARY} ${DLT_LIBRARIES} ${SystemD_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OS_LIBS})
target_link_libraries(${VSOMEIP_NAME}-sd ${VSOMEIP_NAME} ${Boost_LIBRARIES} ${USE_RT} ${DL_LIBRARY} ${SystemD_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OS_LIBS})
target_link_libraries(${VSOMEIP_NAME}-e2e ${VSOMEIP_NAME} ${Boost_LIBRARIES} ${USE_RT} ${DL_LIBRARY} ${SystemD_LIBRARIES} ${OS_LIBS})

CMakeLists.txt修改完成后,源码中还有一些地方需要修改:vsomeip/implementation/configuration/internal_android.hpp 依赖项
修改完成后,我们启动对app模块的编译,就会在根目录发现生成的协议栈库了
在这里插入图片描述
上述所有的修改均包含在文章末尾的源码链接库中,下载后可以直接编译使用。


三、了解API

之前在介绍SOME/IP协议的时候有提到过SOME/IP中有三个比较重要的概念,分别是属性(Field), 方法(Method), 以及事件(Event),这里再重复的点一下,带着这个概念去理解API可能会更加深刻:

方法(Method):类似于我们写java类中的定义的函数,函数可以有返回值,可以没有返回值。一个类的实例中的公共方法可以被其他模块调用。vsomeip中的方法也是如此,无返回值的叫做FF方法,有返回值的叫做RR方法。

事件(Event):事件就类似于java中的监听器,当有模块触发回调的时候,监听方能够及时得到响应。事件可以周期性触发(Cycle change),对于属性事件来说,还能定义在值变化时触发(Update on change),以及更新值大于(当前属性值 + ε)时触发(Epsilon change )

属性:这个就更好理解了,类似于java类中定义的属性,有get,set方法,如果不是只读属性,那么其还包括一个值变化的事件。

下图是vsomeip协议栈中一些常见的API列表以及方法的功能。

在这里插入图片描述


四、代码实现通信

快速过一下API接口后,我们需要实现一个例子,用来了解vsomeip的常规使用方式,这里我们通过实现两个linux程序来实现,一个作为SOME/IP client端,用来请求服务,并且通过服务实例调用server端的方法, 一个作为SOME/IP server端,用来实现服务,并且响应client端的request请求,话不多说,开干。
app/src/main/cpp文件夹下新建两个文件,

someip_server.cpp

#include <string>
#include <vsomeip/vsomeip.hpp>
#include <csignal>
#include <unistd.h>

//服务ID
static vsomeip::service_t  weather_service_id = 0x1001;
//服务实例ID
static vsomeip::instance_t weather_service_instance_id = 0x0001;
//方法ID
static vsomeip::method_t   weather_get_temp_method_id = 0x0001;

static std::shared_ptr<vsomeip::application> app_;

//处理message消息
static void on_message_callback(const std::shared_ptr<vsomeip::message>& msg){
    printf("%s message = ", __func__);
    vsomeip::byte_t *data = msg->get_payload()->get_data();
    for(int i=0; i< msg->get_payload()->get_length(); i++){
        printf("%02x ", data[i]);
    }
    printf("\n");
}

//处理状态消息
static void on_state_callback(vsomeip::state_type_e state){
    printf("%s state = %hhu\n", __func__ , state);
    if(state == vsomeip::state_type_e::ST_REGISTERED){
        app_->offer_service(weather_service_id, weather_service_instance_id);
    }
}

int main(int args, char** argc){
    //设置配置文件路径
    setenv("VSOMEIP_CONFIGURATION", "/vendor/etc/local_server.json", 1);
    //获取vsomeip运行环境
    auto rtm_ = vsomeip::runtime::get();
    //创建一个vsomeip app
    app_ = rtm_->create_application("someip_server");
    //初始化
    if(!app_->init()){
        return 0;
    }

    //初始花完成后,注册消息回调,event, method,以及attribute的set,get,notify等消息都是走这里
    app_->register_message_handler(weather_service_id,
                                   weather_service_instance_id,
                                   weather_get_temp_method_id,
                                   &on_message_callback);

    //注册app状态回调
    app_->register_state_handler(&on_state_callback);
    //启动app
    app_->start();
    while(true){
        usleep(1000 * 1000);
    }
    return 1;
}

someip_client.cpp

#include <string>
#include <vsomeip/vsomeip.hpp>
#include <csignal>
#include <unistd.h>
#include "thread"

static vsomeip::service_t  weather_service_id = 0x1001;
static vsomeip::instance_t weather_service_instance_id = 0x0001;
static vsomeip::method_t   weather_get_temp_method_id = 0x0001;

int main(int args, char** argc){
    setenv("VSOMEIP_CONFIGURATION", "/vendor/etc/local_client.json", 1);
    auto rtm_ = vsomeip::runtime::get();
    auto app_ = rtm_->create_application("someip_client");
    if(!app_->init()){
        printf("init failed\n");
        return 0;
    }

    app_->register_message_handler(weather_service_id,
                                   weather_service_instance_id,
                                   weather_get_temp_method_id,
                                   [](const std::shared_ptr<vsomeip::message>& msg){
                                       printf("MessageCallback : %s\n",msg->get_payload()->get_data());
    });

    app_->register_availability_handler(weather_service_id,
                                        weather_service_instance_id,
                                        [&app_, &rtm_](vsomeip::service_t service, vsomeip::instance_t instance, bool available){
                                            printf("AvailableHandler : service = 0x%02x, instance = 0x%02x , available = %d\n",
                                                   service,
                                                   instance,
                                                   available ? 1 : 0);
                                            if(available){
                                                std::thread send([&rtm_, &app_]{
                                                    int count = 10;
                                                    while(count -- > 0) {
                                                        printf("send::\n");
                                                        auto msg = rtm_->create_request(true);
                                                        msg->set_service(weather_service_id);
                                                        msg->set_instance(weather_service_instance_id);
                                                        msg->set_method(weather_get_temp_method_id);
                                                        std::vector<vsomeip::byte_t> payload_raw = {0x0, 0x0 , static_cast<unsigned char>(count)};
                                                        auto payload = rtm_->create_payload(payload_raw);
                                                        msg->set_payload(payload);
                                                        app_->send(msg);
                                                        usleep(1000 * 1000);
                                                    }
                                                });
                                                send.detach();
                                            }
    });

    app_->register_state_handler([&app_](vsomeip::state_type_e state){
        if(state == vsomeip::state_type_e::ST_REGISTERED){
            app_->request_service(weather_service_id, weather_service_instance_id);
        }
    });

    app_->start();

    while(true){
        usleep(1000 * 1000);
    }
    return 1;
}

然后编写cpp目录下的CMakeLists.txt文件,将这两个文件编译成两个可执行程序

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.18.1)

# Declares and names the project.
project("someip")

find_package (vsomeip3 3.3.8 REQUIRED)
find_library(log-lib log)
include_directories(${VSOMEIP3_INCLUDE_DIRS})

add_executable(someip_server
        # Provides a relative path to your source file(s).
        someip_server.cpp)

target_link_libraries(someip_server ${log-lib} vsomeip3 vsomeip3-e2e vsomeip3-sd)

add_executable(someip_client
        # Provides a relative path to your source file(s).
        someip_client.cpp)

target_link_libraries(someip_client ${log-lib} vsomeip3 vsomeip3-e2e vsomeip3-sd)

编写完成后,即可启动编译,生成的文件在工程根目录的output下面,如下图所示:
在这里插入图片描述
这里我们首先在单机的环境下测试,即两个程序运行在同一系统中,因此接下来准备一下配置文件,
server端的为local_server.json,内容如下:

{
    "unicast":"127.0.0.1",
    "logging":
    {
        "level":"debug",
        "console":"true"
    },

    "applications":
    [
        {
            "name":"someip_server",
            "id":"0x1000"
        }
    ],

    "routing":"someip_server",
    "service-discovery":
    {
        "enable":"false"
    }
}

client端的为local_client.json, 内容如下:

{
    "unicast":"127.0.0.1",
    "logging":
    {
        "level":"debug",
        "console":"true"
    },

    "applications":
    [
        {
            "name":"someip_client",
            "id":"0x1001"
        }
    ],
    "routing":"someip_server",
    "service-discovery":
    {
        "enable":"false"
    }
}

然后我们的工程目录变成了这样:
在这里插入图片描述
把local_server.json根local_client.json使用adb命令push到系统的/vendor/etc/下面
在这里插入图片描述
push.bat 脚本内容

adb root & adb remount
adb push libvsomeip3.so /system/lib64/
adb push libvsomeip3-e2e.so /system/lib64/
adb push libvsomeip3-sd.so /system/lib64/
adb push local_client.json /vendor/etc/
adb push local_server.json /vendor/etc/
adb push someip_client /system/bin/
adb push someip_server /system/bin/
pause:

然后执行命令,即可看到通讯正常启动。
在这里插入图片描述
可以看到我们client端发送的payload,在server端已经正常打印出来了。这是单机通信的情况,是不通过网络的,vsomeip协议栈的路由是使用unix域socket来实现两个进程的跨进程通信,那如果是两个设备之间怎么通信呢? 代码不用改,修改配置文件即可。先看以下两台设备的环境,两台设备在同一个局域网中,通过wlan网卡来通信,server端的网络信息如下:
在这里插入图片描述
client端日志如下所示:
在这里插入图片描述
修改server端配置local_server.json如下:

{
    "unicast":"172.17.6.120",
    "logging":
    {
        "level":"debug",
        "console":"true"
    },

    "applications":
    [
        {
            "name":"someip_server",
            "id":"0x1000"
        }
    ],
    "services" :
    [
        {
            "service" : "0x1001",
            "instance" : "0x0001",
            "reliable" : { "port" : "30509", "enable-magic-cookies" : "false" }
        }
    ],
    "routing":"someip_server",
    "service-discovery" :
    {
        "enable" : "true",
        "multicast" : "239.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"
    }
}

修改客户端配置文件local_client.json文件如下:

{
    "unicast":"172.17.6.141",
    "netmask" : "255.255.255.0",
    "logging":
    {
        "level":"debug",
        "console":"true"
    },

    "applications":
    [
        {
            "name":"someip_client",
            "id":"0x1001"
        }
    ],
    "clients" :
    [
        {
            "service" : "0x1001",
            "instance" : "0x0001",
            "reliable" : [ "41234" ]
        }
    ],
    "routing":"someip_client",
    "service-discovery" :
    {
        "enable" : "true",
        "multicast" : "239.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"
    }
}

然后分别将配置文件push到目标机器的/vendor/etc/目录,两台机器开始启动someip_server跟someip_client,就会看到如下内容打印,成功实现双机通讯
在这里插入图片描述


五、配置项与配置文件

vsomeip的使用过程中,配置文件是很重要的一部分,其定义了诸多的字段来规范vsomeip的行为,具体的字段含义在vsomeip/documentation/vsomeipUserGuide中均有详细描述


六、总结

本篇文章从vsomeip的下载,编译,以及demo编写三个方面展示了vsomeip的协议栈的具体使用方式,相信这一系列操作下来,我们使用协议栈来通信是没有什么问题的,但是我们发现vsomeip的方式收到的包还是一系列的payload,并不太直观,还需要自行解析,下一篇文章,我们来结束解决这一痛点的工具,CommonAPI开源工具

  • 17
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 30
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值