赞
踩
**
**
**
**
**
**
上一篇文章介绍了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新建一个工程,工程类别选择native c++, 编译链选择default, 新建完成后,在工程根目录新建cmake
与external
两个文件夹备用,cmake主要用来存放编译时库查找的脚本,external主要用来存放协议栈与依赖的动态库代码,此时工程目录如下所示
boost-cmake
boost 1.71.0
vsomeip协议栈
这里要说一下,vsomeip的网络通信是基于boost库实现的,因此这里咱们也需要将boost中的一个模块编译成静态库集成
下载完成后,将包解压,拷贝到external目录下,如下图所示。
在cmake目录下按照cmake的规则文档编写FindBoost.cmake
与Findvsomeip3.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中的一些内容,解决库依赖以及一些宏定义导致的问题,
修改根目录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模块的编译,就会在根目录发现生成的协议栈库了
上述所有的修改均包含在文章末尾的源码链接库中,下载后可以直接编译使用。
之前在介绍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开源工具
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。