一、前言
SOME/IP是一套可伸缩的基于IP协议的面向服务的中间件的规范,可以在车载以太网上实现RPC功能(Remote Procedure Call,远程过程调用),这样一个ECU可以通过车载以太网调用另一个ECU上提供的函数。vsomip是由宝马开发的开源SOME/IP协议栈。
SOME/IP官网:Scalable service-Oriented MiddlewarE over IP (SOME/IP)
vsomeip代码仓库:GitHub - COVESA/vsomeip: An implementation of Scalable service-Oriented MiddlewarE over IP
二、下载、编译源码
-
检查gcc版本(vsomeip使用到了C++11,需要gcc 4.8及以上版本):
gcc -v
-
安装cmake(vsomeip使用到了CMake构建系统):
sudo apt install cmake
-
安装boost库到/usr/local路径下(vsomeip使用了Boost库,需要Boost 1.55及以上版本,目前最高支持到Boost 1.76,后面执行vsomeip的cmake命令的时候要留意相关的错误信息):
tar --bzip2 -xf boost_1_76_0.tar.bz2 cd boost_1_78_0/ sudo ./bootstrap.sh --prefix=/usr/local sudo ./b2 install --with=all
-
如果安装的Boost库版本错误,可以执行如下命令删除已安装的boost:
sudo rm -f /usr/local/lib/libboost* sudo rm -rf /usr/local/include/boost sudo rm -rf /usr/local/lib/cmake/*-1.78.0*
-
安装gtest(用于编译vsomeip的单元测试用例):
git clone https://github.com/google/googletest cd googletest cmake CMakeLists.txt make sudo cp ./lib/* /usr/lib sudo cp -a googletest/include/* /usr/include/ sudo cp -a googlemock/include/* /usr/include/
-
安装编译vsomeip内文档所需要的工具和pkg-config工具:
sudo apt-get update sudo apt-get install asciidoc source-highlight doxygen graphviz pkg-config
-
测试boost是否安装成功(执行
g++ test_boost.cpp -o test_boost
命令编译测试程序,执行echo 1 2 3 | ./test_boost
命令运行测试程序):#include <boost/lambda/lambda.hpp> #include <iostream> #include <iterator> #include <algorithm> int main() { using namespace boost::lambda; typedef std::istream_iterator<int> in; std::for_each( in(std::cin), in(), std::cout << (_1 * 3) << " " ); }
-
测试gtest是否安装成功(执行
g++ test_gtest.cpp -lgtest -lpthread -o test_gtest
命令编译测试程序,执行./test_gtest
命令运行测试程序):#include <gtest/gtest.h> int add(int a,int b){ return a+b; } TEST(testCase,test0){ EXPECT_EQ(add(2,3),5); } int main(int argc,char **argv){ testing::InitGoogleTest(&argc,argv); return RUN_ALL_TESTS(); }
-
下载、编译、安装vsomeip库(重新编译记得先删除原来的build目录,执行
make doc
命令会生成vsomeip/build/documentation/vsomeipUserGuide.html文件和vsomeip/build/documentation/html/index.html文件):git clone https://github.com/COVESA/vsomeip.git cd vsomeip mkdir build cd build export GTEST_ROOT=/home/charming/Downloads/googletest cmake .. make -j20 sudo make install make doc
-
可以在编译前通过给cmake命令传参的方式更改vsomeip的默认配置(如单播地址、诊断地址等),具体方法请参考vsomeip顶级目录的README.md文件。
三、编译、运行hello_world例程
vsomeip自带一个hello_world例程,源码位置在examples/hello_world,编译步骤如下:
cd vsomeip/build
cmake --build . --target hello_world
make hello_world_service hello_world_client
先在第一个终端里执行如下命令运行hello_world_service程序(添加LD_LIBRARY_PATH=/home/charming/Documents/vsomeip/build
是为了解决这个Configuration module could not be loaded!
运行时错误信息的):
cd vsomeip/build/examples/hello_world
LD_LIBRARY_PATH=/home/charming/Documents/vsomeip/build \
VSOMEIP_CONFIGURATION=../../../examples/hello_world/helloworld-local.json \
VSOMEIP_APPLICATION_NAME=hello_world_service \
./hello_world_service
2022-02-28 04:51:07.485420 [info] Parsed vsomeip configuration in 0ms
2022-02-28 04:51:07.486476 [info] Using configuration file: "./helloworld-local.json".
2022-02-28 04:51:07.486564 [info] Configuration module loaded.
2022-02-28 04:51:07.486604 [info] Initializing vsomeip application "hello_world_service".
2022-02-28 04:51:07.487442 [info] Instantiating routing manager [Host].
2022-02-28 04:51:07.489528 [info] create_local_server Routing endpoint at /tmp/vsomeip-0
2022-02-28 04:51:07.491667 [info] Application(hello_world_service, 4444) is initialized (11, 100).
2022-02-28 04:51:07.492815 [info] Starting vsomeip application "hello_world_service" (4444) using 2 threads I/O nice 255
2022-02-28 04:51:07.494518 [info] shutdown thread id from application: 4444 (hello_world_service) is: 7f8dc3dff700 TID: 32258
2022-02-28 04:51:07.494435 [info] main dispatch thread id from application: 4444 (hello_world_service) is: 7f8dc4600700 TID: 32257
2022-02-28 04:51:07.496899 [info] Watchdog is disabled!
2022-02-28 04:51:07.498448 [info] io thread id from application: 4444 (hello_world_service) is: 7f8dc6bd0d00 TID: 32255
2022-02-28 04:51:07.498529 [info] io thread id from application: 4444 (hello_world_service) is: 7f8dc2dfd700 TID: 32260
2022-02-28 04:51:07.498719 [info] OFFER(4444): [1111.2222:0.0] (true)
2022-02-28 04:51:07.499362 [info] vSomeIP 3.1.20.3 | (default)
2022-02-28 04:51:07.502779 [info] Listening at /tmp/vsomeip-4444
2022-02-28 04:51:17.506488 [info] vSomeIP 3.1.20.3 | (default)
2022-02-28 04:51:27.514147 [info] vSomeIP 3.1.20.3 | (default)
2022-02-28 04:51:37.516763 [info] vSomeIP 3.1.20.3 | (default)
2022-02-28 04:51:47.523069 [info] vSomeIP 3.1.20.3 | (default)
2022-02-28 04:51:57.525565 [info] vSomeIP 3.1.20.3 | (default)
2022-02-28 04:52:07.534342 [info] vSomeIP 3.1.20.3 | (default)
2022-02-28 04:52:07.729135 [info] Application/Client 5555 is registering.
2022-02-28 04:52:07.730497 [info] Client [4444] is connecting to [5555] at /tmp/vsomeip-5555
2022-02-28 04:52:07.734012 [info] REGISTERED_ACK(5555)
2022-02-28 04:52:07.845123 [info] REQUEST(5555): [1111.2222:255.4294967295]
2022-02-28 04:52:07.854602 [info] RELEASE(5555): [1111.2222]
2022-02-28 04:52:07.854940 [info] Application/Client 5555 is deregistering.
2022-02-28 04:52:07.856428 [info] Client [4444] is closing connection to [5555]
2022-02-28 04:52:12.855090 [info] STOP OFFER(4444): [1111.2222:0.0] (true)
2022-02-28 04:52:12.856448 [info] Stopping vsomeip application "hello_world_service" (4444).
然后在第二个终端里执行如下命令运行hello_world_client程序:
cd vsomeip/build/examples/hello_world
LD_LIBRARY_PATH=/home/charming/Documents/vsomeip/build \
VSOMEIP_CONFIGURATION=../../../examples/hello_world/helloworld-local.json \
VSOMEIP_APPLICATION_NAME=hello_world_client \
./hello_world_client
2022-02-28 04:52:07.718401 [info] Parsed vsomeip configuration in 0ms
2022-02-28 04:52:07.718915 [info] Using configuration file: "../../../examples/hello_world/helloworld-local.json".
2022-02-28 04:52:07.718995 [info] Configuration module loaded.
2022-02-28 04:52:07.719034 [info] Initializing vsomeip application "hello_world_client".
2022-02-28 04:52:07.719077 [info] Instantiating routing manager [Proxy].
2022-02-28 04:52:07.719553 [info] Client [5555] is connecting to [0] at /tmp/vsomeip-0
2022-02-28 04:52:07.720077 [info] Application(hello_world_client, 5555) is initialized (11, 100).
2022-02-28 04:52:07.720329 [info] Starting vsomeip application "hello_world_client" (5555) using 2 threads I/O nice 255
2022-02-28 04:52:07.721646 [info] main dispatch thread id from application: 5555 (hello_world_client) is: 7fe9b477a700 TID: 32263
2022-02-28 04:52:07.722972 [info] io thread id from application: 5555 (hello_world_client) is: 7fe9b6549d00 TID: 32262
2022-02-28 04:52:07.723214 [info] io thread id from application: 5555 (hello_world_client) is: 7fe9b3778700 TID: 32265
2022-02-28 04:52:07.722003 [info] shutdown thread id from application: 5555 (hello_world_client) is: 7fe9b3f79700 TID: 32264
2022-02-28 04:52:07.727308 [info] Listening at /tmp/vsomeip-5555
2022-02-28 04:52:07.727787 [info] Client 5555 (hello_world_client) successfully connected to routing ~> registering..
2022-02-28 04:52:07.732502 [info] Application/Client 5555 (hello_world_client) is registered.
2022-02-28 04:52:07.846269 [info] ON_AVAILABLE(5555): [1111.2222:0.0]
Sending: World
2022-02-28 04:52:07.847799 [info] Client [5555] is connecting to [4444] at /tmp/vsomeip-4444
Received: Hello World
2022-02-28 04:52:07.852080 [info] Stopping vsomeip application "hello_world_client" (5555).
2022-02-28 04:52:07.855985 [info] Application/Client 5555 (hello_world_client) is deregistered.
2022-02-28 04:52:07.857304 [info] Client [5555] is closing connection to [4444]
四、vsomeip库中一些常用的类和函数
vsomeip.hpp头文件
-
我们使用vsomeip库的时候只需要添加这一个头文件就可以了
#include <vsomeip/vsomeip.hpp>
,它包含了vsomeip中一些常用类的头文件,以下是文件内容:#ifndef VSOMEIP_VSOMEIP_HPP #define VSOMEIP_VSOMEIP_HPP /** * \brief The central vsomeip header. Include this to use vsomeip. */ #include <vsomeip/constants.hpp> #include <vsomeip/defines.hpp> #include <vsomeip/application.hpp> #include <vsomeip/message.hpp> #include <vsomeip/payload.hpp> #include <vsomeip/runtime.hpp> #include <vsomeip/trace.hpp> namespace vsomeip = vsomeip_v3; #endif // VSOMEIP_VSOMEIP_HPP
runtime类
-
vsomeip::runtime类,这是一个单例类,定义了一些静态成员函数和虚成员函数,如create_application函数、create_request函数、create_response函数、create_payload函数等等,负责管理本机vsomeip库中所有的公共资源,定义在<vsomeip/runtime.hpp>文件内:
/** * * \brief Singleton class containing all public resource management * facilities of vsomeip. * * The methods of this class shall be used to create instances of all the * classes needed to facilitate SOME/IP communication. In particular, it is * the entry point to create instances of the @ref application class that * contains the main public API of vsomeip. * */ class VSOMEIP_IMPORT_EXPORT runtime { public: static std::string get_property(const std::string &_name); static void set_property(const std::string &_name, const std::string &_value); static std::shared_ptr<runtime> get(); virtual ~runtime() { } /** * * \brief Creates a vsomeip application object. * * An application object manages service offers and requests as well as * event subscriptions. It allows to register user application functions * as callbacks that are called on specific events during runtime, e.g * to react on incoming SOME/IP messages. * An application object is identified by a unique name that is also used * in (and therefore has to match) the configuration files of vsomeip. If * the name is left empty, the application name is taken from the * environment variable "VSOMEIP_APPLICATION_NAME" * * \param _name Name of the application on the system. * */ virtual std::shared_ptr<application> create_application( const std::string &_name = "") = 0; ... /** * * \brief Constructs an empty request message. * * The message can then be used to call @ref application::send to send a * SOME/IP message. The message type is set to REQUEST after the * call and the request identifier is automatically set during the * @ref application::send call. * * The user application is responsible for setting the service instance * and the payload. * * \param _reliable Determines whether this message shall be sent * over a reliable connection (TCP) or not (UDP). * */ virtual std::shared_ptr<message> create_request( bool _reliable = false) const = 0; /* * \brief Constructs an empty response message from a given request * message. * * The message can then be used to call @ref application::send to send a * SOME/IP message. The message type is set to RESPONSE after the * call and the request identifier is automatically set from the * request message. * * The user application is responsible for setting the service instance * and the payload. * * \param _request The request message that shall be answered by * the response message. * */ virtual std::shared_ptr<message> create_response( const std::shared_ptr<message> &_request) const = 0; ... /** * * \brief Creates a payload object filled with the given data. * * \param _data Bytes to be copied into the payload object. * */ virtual std::shared_ptr<payload> create_payload( const std::vector<byte_t> &_data) const = 0; ... };
-
runtime类具体的实现是implementation/runtime/include/runtime_impl.hpp文件中定义的runtime_impl类:
class runtime_impl: public runtime { public: static std::string get_property(const std::string &_name); static void set_property(const std::string &_name, const std::string &_value); static std::shared_ptr<runtime> get(); virtual ~runtime_impl(); std::shared_ptr<application> create_application( const std::string &_name); std::shared_ptr<message> create_message(bool _reliable) const; std::shared_ptr<message> create_request(bool _reliable) const; std::shared_ptr<message> create_response( const std::shared_ptr<message> &_request) const; std::shared_ptr<message> create_notification(bool _reliable) const; std::shared_ptr<payload> create_payload() const; std::shared_ptr<payload> create_payload(const byte_t *_data, uint32_t _size) const; std::shared_ptr<payload> create_payload( const std::vector<byte_t> &_data) const; std::shared_ptr<application> get_application( const std::string &_name) const; void remove_application( const std::string &_name); private: static std::map<std::string, std::string> properties_; std::map<std::string, std::weak_ptr<application>> applications_; mutable std::mutex applications_mutex_; };
-
其中值得注意的是runtime_impl::get和runtime_impl::create_application函数的实现(位于implementation/runtime/src/runtime_impl.cpp文件):
std::shared_ptr<runtime> runtime_impl::get() { static std::shared_ptr<runtime> the_runtime_ = std::make_shared<runtime_impl>(); return the_runtime_; } std::shared_ptr<application> runtime_impl::create_application( const std::string &_name) { static std::uint32_t postfix_id = 0; std::lock_guard<std::mutex> its_lock(applications_mutex_); std::string its_name_ = _name; auto found_application = applications_.find(_name); if( found_application != applications_.end()) { its_name_ += "_" + std::to_string(postfix_id++); } std::shared_ptr<application> application = std::make_shared<application_impl>(its_name_); applications_[its_name_] = application; return application; }
application类
-
vsomeip::application类,每个使用vsomeip库的应用程序都需要有一个application对象,用来调用vsomeip库提供的一系列API,application类在<vsomeip/application.hpp中>定义:
/** * * \brief This class contains the public API of the vsomeip implementation. * * Due to its heavy resource footprint, it should exist once per client and can * be instantiated using the API of @ref runtime. It manages the lifecycle of * the vsomeip client and allocates all resources needed to communicate. * */ class application { public: ... /** * * \brief Initializes the application. * * The init method must be called first after creating a vsomeip * application and executes the following steps to initialize it: * - Loading the configuration from a dynamic module * - Loading the configuration from a .json file or * - Loading the configuration from compiled data (not yet available) * - Determining routing configuration and initialization of the routing * itself * - Installing signal handlers * */ virtual bool init() = 0; /** * * \brief Starts message processing. * * This method must be called after init to start message processing. It * will block until the message processing is terminated using the @ref * stop method or by receiving signals. It processes messages received * via the sockets and uses registered callbacks to pass them to the user * application. * */ virtual void start() = 0; /** * \brief Stops message processing. * * This method stops message processing. Thus, @ref start will return * after a call to stop. * */ virtual void stop() = 0; ... /** * * \brief Offers a SOME/IP service instance. * * The user application must call this method for each service it offers * to register it at the vsomeip routing component, which makes the * service visible to interested clients. Dependent on the configuration * the service is available internally only or internally and externally. * To offer a service to the external network, the configuration must * contain a port for the offered service instance. If no such port * configuration is provided, the service is not visible outside the * device. * * \param _service Service identifier of the offered service interface. * \param _instance Instance identifier of the offered service instance. * \param _major Major service version (Default: 0). * \param _minor Minor service version (Default: 0). * */ virtual void offer_service(service_t _service, instance_t _instance, major_version_t _major = DEFAULT_MAJOR, minor_version_t _minor = DEFAULT_MINOR) = 0; /** * * \brief Stops offering a SOME/IP service instance. * * The user application must call this method to withdraw a service offer. * * \param _service Service identifier of the offered service interface. * \param _instance Instance identifer of the offered service instance. * \param _major Major service version (Default: 0). * \param _minor Minor service version (Default: 0). * */ virtual void stop_offer_service(service_t _service, instance_t _instance, major_version_t _major = DEFAULT_MAJOR, minor_version_t _minor = DEFAULT_MINOR) = 0; ... /** * * \brief Registers the application as client of a service instance. * * A user application must call this method for each service instance it * wants to use. The request is stored within the routing component and the * application is registered as client for the service as soon as the * service instance becomes available. * * \param _service Service identifier of the requested service interface. * \param _instance Instance identifier of the requested service instance. * \param _major Major service version (Default: 0xFF). * \param _minor Minor service version (Default: 0xFFFFFF). * \param _use_exclusive_proxy Create an IP endpoint that is exclusively * used for the communication of this application to the service instance. * */ virtual void request_service(service_t _service, instance_t _instance, major_version_t _major = ANY_MAJOR, minor_version_t _minor = ANY_MINOR) = 0; /** * * \brief Unregister the application as client of a service instance. * * A user application should call this method if it does not request to * use the service instance any longer. The method unregisters the request * a the routing component, which removes the service instance from the * list of requested service instances if the call releases the last * existing request for the service instance. This is important for * external service instances, as the SOME/IP Service Discovery can avoid * to send unnecessary Find messages. * * \param _service Service identifier of the offered service interface. * \param _instance Instance identifier of the offered service instance. * */ virtual void release_service(service_t _service, instance_t _instance) = 0; ... /** * * \brief Sends a message. * * Serializes the specified message object, determines the taget and sends * the message to the target. For requests, the request identifier is * automatically built from the client identifier and the session * identifier. * * \param _message Message object. * */ virtual void send(std::shared_ptr<message> _message) = 0; ... /** * * \brief Register a state handler with the vsomeip runtime. * * The state handler tells if this client is successfully [de]registered * at the central vsomeip routing component. This is called during the * @ref start and @ref stop methods of this class to inform the user * application about the registration state. * * \param _handler Handler function to be called on state change. * */ virtual void register_state_handler(state_handler_t _handler) = 0; /** * * \brief Unregister the state handler. * */ virtual void unregister_state_handler() = 0; /** * * \brief Registers a handler for the specified method or event. * * A user application must call this method to register callbacks for * for messages that match the specified service, instance, method/event * pattern. It is possible to specify wildcard values for all three * identifiers arguments. * * Notes: * - Only a single handler can be registered per service, instance, * method/event combination. * - A subsequent call will overwrite an existing registration. * - Handler registrations containing wildcards can be active in parallel * to handler registrations for specific service, instance, method/event * combinations. * * \param _service Service identifier of the service that contains the * method or event. Can be set to ANY_SERVICE to register a handler for * a message independent from a specific service. * \param _instance Instance identifier of the service instance that * contains the method or event. Can be set to ANY_INSTANCE to register * a handler for a message independent from a specific service. * \param _method Method/Event identifier of the method/event that is * to be handled. Can be set to ANY_METHOD to register a handler for * all methods and events. * \param _handler Callback that will be called if a message arrives * that matches the specified service, instance and method/event * parameters. * */ virtual void register_message_handler(service_t _service, instance_t _instance, method_t _method, message_handler_t _handler) = 0; /** * * \brief Unregisters the message handler for the specified service * method/event notification. * * \param _service Service identifier of the service that contains the * method or event. Can be set to ANY_SERVICE to unregister a handler for * a message independent from a specific service. * \param _instance Instance identifier of the service instance that * contains the method or event. Can be set to ANY_INSTANCE to unregister * a handler for a message independent from a specific service. * \param _method Method/Event identifier of the method/event that is * to be handled. Can be set to ANY_METHOD to unregister a handler for * all methods and events. */ virtual void unregister_message_handler(service_t _service, instance_t _instance, method_t _method) = 0; /** * * \brief Register a callback that is called when service instances * availability changes. * * This method allows for the registration of callbacks that are called * whenever a service appears or disappears. It is possible to specify * wildcards for service, instance and/or version. Additionally, the * version specification is optional and defaults to DEFAULT_MAJOR * /DEFAULT_MINOR. * * \param _service Service identifier of the service instance whose * availability shall be reported. Can be set to ANY_SERVICE. * \param _instance Instance identifier of the service instance whose * availability shall be reported. Can be set to ANY_INSTANCE. * \param _handler Callback to be called if availability changes. * \param _major Major service version. The parameter defaults to * DEFAULT_MAJOR and can be set to ANY_MAJOR. * \param _minor Minor service version. The parameter defaults to * DEFAULT_MINOR and can be set to ANY_MINOR. * */ virtual void register_availability_handler(service_t _service, instance_t _instance, availability_handler_t _handler, major_version_t _major = ANY_MAJOR, minor_version_t _minor = ANY_MINOR) = 0; ... /** * * \brief Unregister all registered handlers. * */ virtual void clear_all_handler() = 0; ... };
-
application类具体的实现是implementation/runtime/include/application_impl.hpp文件中定义的application_impl类:
class application_impl: public application, public routing_manager_host, public std::enable_shared_from_this<application_impl> { public: VSOMEIP_EXPORT application_impl(const std::string &_name); ... };
handler.hpp头文件
-
可以注意到使用application类的register_state_handler成员函数、register_message_handler成员函数、register_availability_handler成员函数传入回调函数时分别使用到了state_handler_t、message_handler_t、availability_handler_t这三个类型,使用vsomeip库需要用到的回调函数类型都定义在<vsomeip/handler.hpp>头文件内:
#ifndef VSOMEIP_V3_HANDLER_HPP_ #define VSOMEIP_V3_HANDLER_HPP_ #include <functional> #include <memory> #include <tuple> #include <vsomeip/primitive_types.hpp> namespace vsomeip_v3 { class message; typedef std::function< void (state_type_e) > state_handler_t; typedef std::function< void (const std::shared_ptr< message > &) > message_handler_t; typedef std::function< void (service_t, instance_t, bool) > availability_handler_t; ... } // namespace vsomeip_v3 #endif // VSOMEIP_V3_HANDLER_HPP_
primitive_types.hpp头文件
-
vsomeip中用到的其它一些特殊变量类型都定义在<vsomeip/primitive_types.hpp>头文件中:
... namespace vsomeip_v3 { typedef uint32_t message_t; typedef uint16_t service_t; typedef uint16_t method_t; typedef uint16_t event_t; typedef uint16_t instance_t; typedef uint16_t eventgroup_t; typedef uint8_t major_version_t; typedef uint32_t minor_version_t; typedef uint32_t ttl_t; typedef uint32_t request_t; typedef uint16_t client_t; typedef uint16_t session_t; typedef uint32_t length_t; typedef uint8_t protocol_version_t; typedef uint8_t interface_version_t; typedef uint8_t byte_t; ... } // namespace vsomeip_v3 ...
constants.hpp头文件
-
vsomeip中用到的其中一些特殊变量类型对应的常数值都定义在<vsomeip/constants.hpp>头文件中:
... namespace vsomeip_v3 { const major_version_t DEFAULT_MAJOR = 0x00; const minor_version_t DEFAULT_MINOR = 0x00000000; const ttl_t DEFAULT_TTL = 0xFFFFFF; // "until next reboot" const std::string DEFAULT_MULTICAST = "224.0.0.0"; const uint16_t DEFAULT_PORT = 30500; const uint16_t ILLEGAL_PORT = 0; const uint16_t ANY_PORT = 0; const uint16_t NO_TRACE_FILTER_EXPRESSION = 0x0000; const service_t ANY_SERVICE = 0xFFFF; const instance_t ANY_INSTANCE = 0xFFFF; const method_t ANY_METHOD = 0xFFFF; const major_version_t ANY_MAJOR = 0xFF; const minor_version_t ANY_MINOR = 0xFFFFFFFF; ... } // namespace vsomeip_v3 ...
message类
-
vsomeip::message类,该类实现了SOME/IP的标准消息类型,message类定义在interface/vsomeip/message.hpp头文件中:
... /** * \brief Implements regular SOME/IP messages. * * This class extends @ref message_base by an unstructured payload. Except * SOME/IP Service Discovery messages, all SOME/IP messages within vsomeip * are represented by message objects. */ class message: virtual public message_base { public: virtual ~message() {} /** * \brief Returns a pointer to the message payload. */ virtual std::shared_ptr<payload> get_payload() const = 0; /** * \brief Set the message payload. */ virtual void set_payload(std::shared_ptr<payload> _payload) = 0; ... }; ...
-
message类继承了message_base基类,该基类实现了SOME/IP消息类型的头部,并且连接了序列化/反序列化的功能,message_base类定义在<vsomeip/message_base.hpp>头文件中:
... /** * \brief Base class to implement SOME/IP messages. * * This class implements the SOME/IP message header and connects to the * serialzing/deserializing functionalities. The class is inherited by * the message classes within ::vsomeip and vsomeip::sd that add the * payload representations for regular and Service Discovery messages. */ class message_base : public serializable, public deserializable { public: ... /** * \brief Set the service identifier in the message header. */ VSOMEIP_EXPORT virtual void set_service(service_t _service) = 0; ... /** * \brief Set the instance identifier in the message header. * * To address the correct service instance, vsomeip uses the instance * identifier. For external services it is mapped to a IP address and port * combination before the message is sent. For internal messages is * transferred as additional data appended to the SOME/IP messages. * Therefore, before sending a message, a user application must set the * instance identifier. */ VSOMEIP_EXPORT virtual void set_instance(instance_t _instance) = 0; ... /** * \brief Set the method/event identifier in the message header. */ VSOMEIP_EXPORT virtual void set_method(method_t _method) = 0; ... }; ...
payload类
-
vsomeip::payload类,message类中get_payload成员函数和set_payload成员函数分别使用了payload类的智能指针作为返回类型和输入类型,payload类定义在interface/vsomeip/payload.hpp文件内:
... /** * * \brief This class implements an array of bytes to be used as * payload for SOME/IP messages. * */ class payload: public serializable, public deserializable { public: VSOMEIP_EXPORT virtual ~payload() {} /** * \brief Returns true if the given payload is equal to this one. * * \param _other Payload that shall be compared to this payload. */ VSOMEIP_EXPORT virtual bool operator ==(const payload &_other) = 0; /** * \brief Returns pointer to the payload content */ VSOMEIP_EXPORT virtual byte_t * get_data() = 0; /** * \brief Returns constant pointer to the payload content */ VSOMEIP_EXPORT virtual const byte_t * get_data() const = 0; /** * \brief Copies the given data array to the payload object. * * The current payload content is replaced by the data provided. * The given buffer remains untouched. * * \param _data Pointer to a data buffer. * \param _length Length of the data buffer. */ VSOMEIP_EXPORT virtual void set_data(const byte_t *_data, length_t _length) = 0; /** * \brief Copies the given data array to the payload object. * * The current payload content is replaced by the data provided. * The given buffer remains untouched. * * \param _data Vector containing the data */ VSOMEIP_EXPORT virtual void set_data( const std::vector<byte_t> &_data) = 0; /** * \brief Returns the length of the payload content. */ VSOMEIP_EXPORT virtual length_t get_length() const = 0; /** * \brief Set the maximum length of the payload content. * * This function must be called before directly copying data using the * pointer to the internal buffer. */ VSOMEIP_EXPORT virtual void set_capacity(length_t _length) = 0; /** * \brief Moves the given data array to the payload object. * * The current payload content is replaced by the data provided. * The given buffer is owned by the payload object afterwards. * * \param _data Vector containing the data */ VSOMEIP_EXPORT virtual void set_data( std::vector<byte_t> &&_data) = 0; }; ...
五、解析hello_world_service源码
hello_world_service程序包含两个文件:hello_world_service.hpp和hello_world_service_main.cpp,实现的功能是监听从hello_world_client发来的消息,然后在消息内容前面添加"Hello "后再发回给hello_world_client,最后程序会自动退出。
-
hello_world_service.hpp文件中定义了一个hello_world_service类,这个类有如下几个私有成员:
private: std::shared_ptr<vsomeip::runtime> rtm_; std::shared_ptr<vsomeip::application> app_; bool stop_; std::mutex mutex_; std::condition_variable condition_; std::thread stop_thread_;
其中
rtm_
和app_
这两个指针会在hello_world_service类的构造函数内被初始化,hello_world_service类正是通过这两个指针来调用vsomeip库提供的函数的。其中stop_thread_
是用来执行stop成员函数的线程,在hello_world_service类的构造函数内被初始化,在析构函数内被注销。其中stop_
、mutex_
、condition_
这三个变量用于stop线程和main线程之间的同步。需要特别说明的是on_state_cbk成员函数,它会在start成员函数被调用后被vsomeip库回调,作用是告诉hello_world_service类vsomeip库的路由组件已经成功注册或注销了,其它成员函数代码中的注释都有详细说明。 -
hello_world_service_main.cpp文件中实例化了一个名为hw_srv的hello_world_service类对象(该类的构造函数会新建一个线程执行stop成员函数,stop成员函数会在on_message_cbk函数执行完5秒后注销vsomeip服务,或者控制台按Ctrl+C或Ctrl+Z也会调用terminate函数使stop成员函数注销vsomeip服务),然后调用了hw_srv.init函数和hw_srv.start函数,vsomeip服务被注销后hw_srv.start函数才退出,最后main函数退出之前会自动调用hw_srv对象的析构函数注销stop线程。
-
这是hello_world_service.hpp的源码:
// Copyright (C) 2015-2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include <vsomeip/vsomeip.hpp> #include <chrono> #include <thread> #include <condition_variable> #include <mutex> #if defined ANDROID || defined __ANDROID__ #include "android/log.h" #define LOG_TAG "hello_world_service" #define LOG_INF(...) fprintf(stdout, __VA_ARGS__), fprintf(stdout, "\n"), (void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, ##__VA_ARGS__) #define LOG_ERR(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n"), (void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ##__VA_ARGS__) #else #include <cstdio> #define LOG_INF(...) fprintf(stdout, __VA_ARGS__), fprintf(stdout, "\n") #define LOG_ERR(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n") #endif static vsomeip::service_t service_id = 0x1111; static vsomeip::instance_t service_instance_id = 0x2222; static vsomeip::method_t service_method_id = 0x3333; class hello_world_service { public: // Get the vSomeIP runtime and // create a application via the runtime, we could pass the application name // here otherwise the name supplied via the VSOMEIP_APPLICATION_NAME // environment variable is used hello_world_service() : rtm_(vsomeip::runtime::get()), app_(rtm_->create_application()), stop_(false), stop_thread_(std::bind(&hello_world_service::stop, this)) { } ~hello_world_service() { stop_thread_.join(); } bool init() { // init the application if (!app_->init()) { LOG_ERR("Couldn't initialize application"); return false; } // register a message handler callback for messages sent to our service app_->register_message_handler(service_id, service_instance_id, service_method_id, std::bind(&hello_world_service::on_message_cbk, this, std::placeholders::_1)); // register a state handler to get called back after registration at the // runtime was successful app_->register_state_handler( std::bind(&hello_world_service::on_state_cbk, this, std::placeholders::_1)); return true; } void start() { // start the application and wait for the on_event callback to be called // this method only returns when app_->stop() is called app_->start(); } void stop() { std::unique_lock<std::mutex> its_lock(mutex_); while(!stop_) { condition_.wait(its_lock); } std::this_thread::sleep_for(std::chrono::seconds(5)); // Stop offering the service app_->stop_offer_service(service_id, service_instance_id); // unregister the state handler app_->unregister_state_handler(); // unregister the message handler app_->unregister_message_handler(service_id, service_instance_id, service_method_id); // shutdown the application app_->stop(); } void terminate() { std::lock_guard<std::mutex> its_lock(mutex_); stop_ = true; condition_.notify_one(); } void on_state_cbk(vsomeip::state_type_e _state) { if(_state == vsomeip::state_type_e::ST_REGISTERED) { // we are registered at the runtime and can offer our service app_->offer_service(service_id, service_instance_id); } } void on_message_cbk(const std::shared_ptr<vsomeip::message> &_request) { // Create a response based upon the request std::shared_ptr<vsomeip::message> resp = rtm_->create_response(_request); // Construct string to send back std::string str("Hello "); str.append( reinterpret_cast<const char*>(_request->get_payload()->get_data()), 0, _request->get_payload()->get_length()); // Create a payload which will be sent back to the client std::shared_ptr<vsomeip::payload> resp_pl = rtm_->create_payload(); std::vector<vsomeip::byte_t> pl_data(str.begin(), str.end()); resp_pl->set_data(pl_data); resp->set_payload(resp_pl); // Send the response back app_->send(resp); // we have finished terminate(); } private: std::shared_ptr<vsomeip::runtime> rtm_; std::shared_ptr<vsomeip::application> app_; bool stop_; std::mutex mutex_; std::condition_variable condition_; std::thread stop_thread_; };
-
这是hello_world_service_main.cpp的源码:
// Copyright (C) 2015-2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef VSOMEIP_ENABLE_SIGNAL_HANDLING #include <csignal> #endif #include <vsomeip/vsomeip.hpp> #include "hello_world_service.hpp" #ifndef VSOMEIP_ENABLE_SIGNAL_HANDLING hello_world_service *hw_srv_ptr(nullptr); void handle_signal(int _signal) { if (hw_srv_ptr != nullptr && (_signal == SIGINT || _signal == SIGTERM)) hw_srv_ptr->terminate(); } #endif int main(int argc, char **argv) { (void)argc; (void)argv; hello_world_service hw_srv; #ifndef VSOMEIP_ENABLE_SIGNAL_HANDLING hw_srv_ptr = &hw_srv; signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); #endif if (hw_srv.init()) { hw_srv.start(); return 0; } else { return 1; } }
六、解析hello_world_client源码
hello_world_client程序包含两个文件:hello_world_client.hpp和hello_world_client_main.cpp,实现的功能是等待hello_world_service提供的服务变得可用,然后向其发送内容为"World"的消息,收到hello_world_service的回复消息后将内容输出到控制台并退出程序。
-
hello_world_client.hpp文件中定义了一个hello_world_client类,不同之处在于多了一个名为on_availability_cbk的回调函数,on_availability_cbk函数会在指定的服务变得可用或者变得不可用的时候被vsomeip库回调。
-
这是hello_world_client.hpp的源码:
// Copyright (C) 2015-2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include <vsomeip/vsomeip.hpp> #if defined ANDROID || defined __ANDROID__ #include "android/log.h" #define LOG_TAG "hello_world_client" #define LOG_INF(...) fprintf(stdout, __VA_ARGS__), fprintf(stdout, "\n"), (void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, ##__VA_ARGS__) #define LOG_ERR(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n"), (void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ##__VA_ARGS__) #else #include <cstdio> #define LOG_INF(...) fprintf(stdout, __VA_ARGS__), fprintf(stdout, "\n") #define LOG_ERR(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n") #endif static vsomeip::service_t service_id = 0x1111; static vsomeip::instance_t service_instance_id = 0x2222; static vsomeip::method_t service_method_id = 0x3333; class hello_world_client { public: // Get the vSomeIP runtime and // create a application via the runtime, we could pass the application name // here otherwise the name supplied via the VSOMEIP_APPLICATION_NAME // environment variable is used hello_world_client() : rtm_(vsomeip::runtime::get()), app_(rtm_->create_application()) { } bool init(){ // init the application if (!app_->init()) { LOG_ERR ("Couldn't initialize application"); return false; } // register a state handler to get called back after registration at the // runtime was successful app_->register_state_handler( std::bind(&hello_world_client::on_state_cbk, this, std::placeholders::_1)); // register a callback for responses from the service app_->register_message_handler(vsomeip::ANY_SERVICE, service_instance_id, vsomeip::ANY_METHOD, std::bind(&hello_world_client::on_message_cbk, this, std::placeholders::_1)); // register a callback which is called as soon as the service is available app_->register_availability_handler(service_id, service_instance_id, std::bind(&hello_world_client::on_availability_cbk, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); return true; } void start() { // start the application and wait for the on_event callback to be called // this method only returns when app_->stop() is called app_->start(); } void on_state_cbk(vsomeip::state_type_e _state) { if(_state == vsomeip::state_type_e::ST_REGISTERED) { // we are registered at the runtime now we can request the service // and wait for the on_availability callback to be called app_->request_service(service_id, service_instance_id); } } void on_availability_cbk(vsomeip::service_t _service, vsomeip::instance_t _instance, bool _is_available) { // Check if the available service is the the hello world service if(service_id == _service && service_instance_id == _instance && _is_available) { // The service is available then we send the request // Create a new request std::shared_ptr<vsomeip::message> rq = rtm_->create_request(); // Set the hello world service as target of the request rq->set_service(service_id); rq->set_instance(service_instance_id); rq->set_method(service_method_id); // Create a payload which will be sent to the service std::shared_ptr<vsomeip::payload> pl = rtm_->create_payload(); std::string str("World"); std::vector<vsomeip::byte_t> pl_data(std::begin(str), std::end(str)); pl->set_data(pl_data); rq->set_payload(pl); // Send the request to the service. Response will be delivered to the // registered message handler LOG_INF("Sending: %s", str.c_str()); app_->send(rq); } } void on_message_cbk(const std::shared_ptr<vsomeip::message> &_response) { if(service_id == _response->get_service() && service_instance_id == _response->get_instance() && vsomeip::message_type_e::MT_RESPONSE == _response->get_message_type() && vsomeip::return_code_e::E_OK == _response->get_return_code()) { // Get the payload and print it std::shared_ptr<vsomeip::payload> pl = _response->get_payload(); std::string resp = std::string( reinterpret_cast<const char*>(pl->get_data()), 0, pl->get_length()); LOG_INF("Received: %s", resp.c_str()); stop(); } } void stop() { // unregister the state handler app_->unregister_state_handler(); // unregister the message handler app_->unregister_message_handler(vsomeip::ANY_SERVICE, service_instance_id, vsomeip::ANY_METHOD); // alternatively unregister all registered handlers at once app_->clear_all_handler(); // release the service app_->release_service(service_id, service_instance_id); // shutdown the application app_->stop(); } private: std::shared_ptr<vsomeip::runtime> rtm_; std::shared_ptr<vsomeip::application> app_; };
-
这是hello_world_client_main.cpp的源码:
// Copyright (C) 2015-2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef VSOMEIP_ENABLE_SIGNAL_HANDLING #include <csignal> #endif #include <vsomeip/vsomeip.hpp> #include "hello_world_client.hpp" #ifndef VSOMEIP_ENABLE_SIGNAL_HANDLING hello_world_client *hw_cl_ptr(nullptr); void handle_signal(int _signal) { if (hw_cl_ptr != nullptr && (_signal == SIGINT || _signal == SIGTERM)) hw_cl_ptr->stop(); } #endif int main(int argc, char **argv) { (void)argc; (void)argv; hello_world_client hw_cl; #ifndef VSOMEIP_ENABLE_SIGNAL_HANDLING hw_cl_ptr = &hw_cl; signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); #endif if (hw_cl.init()) { hw_cl.start(); return 0; } else { return 1; } }
七、vsomeip配置文件的格式
-
hello_world例程使用的配置文件名为helloworld-local.json,内容如下:
{ "unicast":"134.86.56.94", "logging": { "level":"debug", "console":"true" }, "applications": [ { "name":"hello_world_service", "id":"0x4444" }, { "name":"hello_world_client", "id":"0x5555" } ], "services": [ { "service":"0x1111", "instance":"0x2222", "unreliable":"30509" } ], "routing":"hello_world_service", "service-discovery": { "enable":"false" } }
-
vsomeip库的配置文件是json格式的,在application类的init成员函数内读取配置文件并使之生效,如果VSOMEIP_CONFIGURATION环境变量没有指定配置文件的路径的话,那么vsomeip将会使用/etc/vsomeip.json这个文件或者/etc/vsomeip目录下的json文件作为配置文件;
-
配置文件的每个元素名及其值的具体含义可以参考vsomeip/build/documentation/vsomeipUserGuide.html文件的第四章Configuration File Structure,接下来只介绍几个hello_world例程所用到的几个配置项;
-
unicast:主机的IP地址;
-
logging->level:日志等级,可取的值有trace、debug、info、warning、error、fatal;
-
logging->console:指定日志是否从控制台输出,可取的值有true或false;
-
applications数组:包含了主机上使用这个配置文件的一系列程序,name指定了程序的名字,id就是一个任意且唯一的16位数字(高位与诊断地址相同时,低位不能为0);
-
services数组:service指定了service的id,instance指定了service instance的id,unreliable指定了UDP的端口(也可以替换为reliable,用port这个子元素指定TCP使用的端口,用enable-magic-cookies这个子元素指定是否使用magic cookies,可取值为true或false),注意端口不要与其它程序冲突;
-
注意在hello_world_service.hpp文件和hello_world_client.hpp文件中都定义了如下三个相同的常量:
static vsomeip::service_t service_id = 0x1111; static vsomeip::instance_t service_instance_id = 0x2222; static vsomeip::method_t service_method_id = 0x3333;
-
routing:负责路由的程序的名字;
-
service-discovery:包含了主程序中与服务发现相关的设置,enable指定是否开启服务发现(可取值有true和false,默认值是true)。