diff --git a/ecal/core/src/service/ecal_service_client_impl.cpp b/ecal/core/src/service/ecal_service_client_impl.cpp index 1124ee0f72..3a3bd20e10 100644 --- a/ecal/core/src/service/ecal_service_client_impl.cpp +++ b/ecal/core/src/service/ecal_service_client_impl.cpp @@ -751,7 +751,12 @@ namespace eCAL const auto port_to_use = (protocol_version == 0 ? iter.tcp_port_v0 : iter.tcp_port_v1); // Create the client and add it to the map - const auto new_client_session = client_manager->create_client(static_cast(protocol_version), iter.hname, port_to_use, event_callback); + const std::vector> endpoint_list + { + {iter.hname, port_to_use}, + {iter.hname + ".local", port_to_use}, // TODO: Make this configurable from the ecal.ini + }; + const auto new_client_session = client_manager->create_client(static_cast(protocol_version), endpoint_list, event_callback); if (new_client_session) m_client_map[iter.key] = new_client_session; } diff --git a/ecal/service/CMakeLists.txt b/ecal/service/CMakeLists.txt index 83a34bb9c0..8743239f47 100644 --- a/ecal/service/CMakeLists.txt +++ b/ecal/service/CMakeLists.txt @@ -16,15 +16,20 @@ # # ========================= eCAL LICENSE ================================= +cmake_minimum_required(VERSION 3.16) +project(ecal_service) + # Main library add_subdirectory(ecal_service) # Samples if(ECAL_CORE_BUILD_SAMPLES) - add_subdirectory(sample) + add_subdirectory(samples/sample_client) + add_subdirectory(samples/sample_server) + add_subdirectory(samples/sample_standalone) endif() # Tests if(ECAL_CORE_BUILD_TESTS) add_subdirectory(test) -endif() \ No newline at end of file +endif() diff --git a/ecal/service/ecal_service/include/ecal/service/client_manager.h b/ecal/service/ecal_service/include/ecal/service/client_manager.h index 39170c3bc1..0bb903c947 100644 --- a/ecal/service/ecal_service/include/ecal/service/client_manager.h +++ b/ecal/service/ecal_service/include/ecal/service/client_manager.h @@ -23,10 +23,16 @@ #include #include #include - -#include #include #include +#include +#include + +#include + +#include + +#include // IWYU pragma: export namespace eCAL { @@ -141,16 +147,14 @@ namespace eCAL * stopped from this central place. * * @param protocol_version The protocol version to use for the client session. If 0, the legacy buggy protocol will be used. - * @param address The address of the server to connect to - * @param port The port of the server to connect to + * @param server_list A list of endpoints to connect to. Must not be empty. The endpoints will be tried in the given order until a working endpoint is found. * @param event_callback The callback, that will be called, when the client has connected to the server or disconnected from it. The callback will be executed in the io_context thread. * * @return A shared_ptr to the newly created ClientSession instance */ - std::shared_ptr create_client(std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const ClientSession::EventCallbackT& event_callback); + std::shared_ptr create_client(std::uint8_t protocol_version + , const std::vector>& server_list + , const ClientSession::EventCallbackT& event_callback); /** * @brief Returns the number of managed client sessions diff --git a/ecal/service/ecal_service/include/ecal/service/client_session.h b/ecal/service/ecal_service/include/ecal/service/client_session.h index c7edeef3a0..6b9b7f52de 100644 --- a/ecal/service/ecal_service/include/ecal/service/client_session.h +++ b/ecal/service/ecal_service/include/ecal/service/client_session.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #ifdef _MSC_VER #pragma warning(push) @@ -34,6 +36,7 @@ #endif #include +#include #include #include @@ -133,43 +136,38 @@ namespace eCAL * * @param io_context The io_context to use for the session and all callbacks. * @param protocol_version The protocol version to use for the session. When this is 0, the legacy buggy protocol is used. - * @param address The address of the server to connect to. May be an IP or a Hostname, IPv6 is supported. - * @param port The port of the server to connect to. + * @param server_list A list of endpoints to connect to. Must not be empty. The endpoints will be tried in the given order until a working endpoint is found. * @param event_callback The callback to be called when the session's state changes, i.e. when the session successfully connected to a server or disconnected from it. * @param logger The logger to use for logging. * @param delete_callback The callback to be called when the session is deleted. This is useful for the eCAL::service::ClientManager to keep track of the number of active sessions. * * @return The new ClientSession instance as a shared_ptr. */ - static std::shared_ptr create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger - , const DeleteCallbackT& delete_callback); + static std::shared_ptr create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger + , const DeleteCallbackT& delete_callback); - static std::shared_ptr create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger = default_logger("Service Client")); + static std::shared_ptr create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger = default_logger("Service Client")); - static std::shared_ptr create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const DeleteCallbackT& delete_callback); + static std::shared_ptr create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const DeleteCallbackT& delete_callback); protected: - ClientSession(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger); + ClientSession(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger); public: // Delete copy constructor and assignment operator @@ -227,28 +225,41 @@ namespace eCAL eCAL::service::Error call_service(const std::shared_ptr& request, std::shared_ptr& response); /** - * @brief Get the address that this client session has been created with. + * @brief Get the host that this client is connected to. * - * This function returns the address that this client session has been - * created with. It will not return the address of the server that this - * client session is connected to, which would actually be the same - * address, but probably resolved to an IP. + * Get the host that this client is connected to. + * If the client is not connected, this function will return an empty + * string. Otherwise, it will return the hostname from the list + * server_list that the client is connected to. * - * @return The address that this client session has been created with. + * The host is not resolved to an IP address. Use get_remote_endpoint() + * to get the actual IP address. + * + * @return The host that this client is connected to. */ - std::string get_address() const; + std::string get_host() const; /** - * @brief Get the port that this client session has been created with. + * @brief Get the port that this client session is connected to. * - * This function returns the port that this client session has been - * created with. It is not said, that the connection has been established - * successfully. + * Get the port that this client session is connected to. If the client + * is not connected, this function will return 0. Otherwise, it will + * return the port from the list server_list that the client is connected + * to. * - * @return The port that this client session has been created with. + * @return The port that this client is connected to */ std::uint16_t get_port() const; + /** + * @brief Get the remote endpoint that this client session is connected to. + * + * Get the remote endpoint that this client session is connected to. Only + * valid, if the client session is actually connected to a server. If a + * hostname was given, this function will return the resolved IP address. + */ + asio::ip::tcp::endpoint get_remote_endpoint() const; + /** * @brief Get the state of this client session. * diff --git a/ecal/service/ecal_service/include/ecal/service/server_manager.h b/ecal/service/ecal_service/include/ecal/service/server_manager.h index fa93457a46..f54c4bb7fa 100644 --- a/ecal/service/ecal_service/include/ecal/service/server_manager.h +++ b/ecal/service/ecal_service/include/ecal/service/server_manager.h @@ -23,10 +23,14 @@ #include #include #include - -#include #include +#include + +#include + +#include // IWYU pragma: export + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/client_manager.cpp b/ecal/service/ecal_service/src/client_manager.cpp index 3843fb07c0..57431782a1 100644 --- a/ecal/service/ecal_service/src/client_manager.cpp +++ b/ecal/service/ecal_service/src/client_manager.cpp @@ -17,14 +17,22 @@ * ========================= eCAL LICENSE ================================= */ +#include + #include #include -#include #include #include #include #include +#include + +#include +#include +#include +#include + namespace eCAL { namespace service @@ -52,10 +60,9 @@ namespace eCAL /////////////////////////////////////////////////////// // Public API /////////////////////////////////////////////////////// - std::shared_ptr ClientManager::create_client(std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const ClientSession::EventCallbackT& event_callback) + std::shared_ptr ClientManager::create_client(std::uint8_t protocol_version + , const std::vector>& server_list + , const ClientSession::EventCallbackT& event_callback) { const std::lock_guard lock(client_manager_mutex_); if (stopped_) @@ -74,7 +81,7 @@ namespace eCAL } }; - auto client = ClientSession::create(io_context_, protocol_version, address, port, event_callback, logger_, deleter); + auto client = ClientSession::create(io_context_, protocol_version, server_list, event_callback, logger_, deleter); sessions_.emplace(client.get(), client); return client; } diff --git a/ecal/service/ecal_service/src/client_session.cpp b/ecal/service/ecal_service/src/client_session.cpp index c3a032f0c3..9a84ddbc50 100644 --- a/ecal/service/ecal_service/src/client_session.cpp +++ b/ecal/service/ecal_service/src/client_session.cpp @@ -17,11 +17,21 @@ * ========================= eCAL LICENSE ================================= */ -#include +#include #include + +#include #include #include #include +#include +#include + +#include + +#include +#include +#include #include "client_session_impl_v1.h" #include "client_session_impl_v0.h" @@ -31,13 +41,12 @@ namespace eCAL { namespace service { - std::shared_ptr ClientSession::create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger - , const DeleteCallbackT& delete_callback) + std::shared_ptr ClientSession::create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger + , const DeleteCallbackT& delete_callback) { auto deleter = [delete_callback](ClientSession* session) { @@ -45,43 +54,40 @@ namespace eCAL delete session; // NOLINT(cppcoreguidelines-owning-memory) }; - return std::shared_ptr(new ClientSession(io_context, protocol_version, address, port, event_callback, logger), deleter); + return std::shared_ptr(new ClientSession(io_context, protocol_version, server_list, event_callback, logger), deleter); } - std::shared_ptr ClientSession::create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + std::shared_ptr ClientSession::create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { - return std::shared_ptr(new ClientSession(io_context, protocol_version, address, port, event_callback, logger)); + return std::shared_ptr(new ClientSession(io_context, protocol_version, server_list, event_callback, logger)); } - std::shared_ptr ClientSession::create(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const DeleteCallbackT& delete_callback) + std::shared_ptr ClientSession::create(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const DeleteCallbackT& delete_callback) { - return ClientSession::create(io_context, protocol_version, address, port, event_callback, default_logger("Service Client"), delete_callback); + return ClientSession::create(io_context, protocol_version, server_list, event_callback, default_logger("Service Client"), delete_callback); } - ClientSession::ClientSession(const std::shared_ptr& io_context - , std::uint8_t protocol_version - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + ClientSession::ClientSession(const std::shared_ptr& io_context + , std::uint8_t protocol_version + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { if (protocol_version == 0) { - impl_ = ClientSessionV0::create(io_context, address, port, event_callback, logger); + impl_ = ClientSessionV0::create(io_context, server_list, event_callback, logger); } else { - impl_ = ClientSessionV1::create(io_context, address, port, event_callback, logger); + impl_ = ClientSessionV1::create(io_context, server_list, event_callback, logger); } } @@ -140,11 +146,12 @@ namespace eCAL } } - State ClientSession::get_state() const { return impl_->get_state(); } - std::uint8_t ClientSession::get_accepted_protocol_version() const { return impl_->get_accepted_protocol_version(); } - int ClientSession::get_queue_size() const { return impl_->get_queue_size(); } - std::string ClientSession::get_address() const { return impl_->get_address(); } - std::uint16_t ClientSession::get_port() const { return impl_->get_port(); } - void ClientSession::stop() { impl_->stop(); } + State ClientSession::get_state() const { return impl_->get_state(); } + std::uint8_t ClientSession::get_accepted_protocol_version() const { return impl_->get_accepted_protocol_version(); } + int ClientSession::get_queue_size() const { return impl_->get_queue_size(); } + std::string ClientSession::get_host() const { return impl_->get_host(); } + asio::ip::tcp::endpoint ClientSession::get_remote_endpoint() const { return impl_->get_remote_endpoint(); } + std::uint16_t ClientSession::get_port() const { return impl_->get_port(); } + void ClientSession::stop() { impl_->stop(); } } // namespace service } // namespace eCAL diff --git a/ecal/service/ecal_service/src/client_session_impl_base.h b/ecal/service/ecal_service/src/client_session_impl_base.h index df87e1eedb..6db29e96c1 100644 --- a/ecal/service/ecal_service/src/client_session_impl_base.h +++ b/ecal/service/ecal_service/src/client_session_impl_base.h @@ -32,7 +32,6 @@ #pragma warning(pop) #endif -#include #include #include @@ -74,8 +73,9 @@ namespace eCAL public: virtual bool async_call_service(const std::shared_ptr& request, const ResponseCallbackT& response_callback) = 0; - virtual std::string get_address() const = 0; - virtual std::uint16_t get_port() const = 0; + virtual std::string get_host() const = 0; + virtual std::uint16_t get_port() const = 0; + virtual asio::ip::tcp::endpoint get_remote_endpoint() const = 0; virtual State get_state() const = 0; virtual std::uint8_t get_accepted_protocol_version() const = 0; diff --git a/ecal/service/ecal_service/src/client_session_impl_v0.cpp b/ecal/service/ecal_service/src/client_session_impl_v0.cpp index 739e14b5de..de123b9e3f 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v0.cpp +++ b/ecal/service/ecal_service/src/client_session_impl_v0.cpp @@ -18,19 +18,29 @@ */ #include "client_session_impl_v0.h" +#include "client_session_impl_base.h" #include "protocol_v0.h" +#include "protocol_layout.h" #include "log_helpers.h" #include "log_defs.h" +#include +#include +#include +#include + #include #include -#include #include #include +#include #include +#include #include +#include + namespace eCAL { namespace service @@ -38,27 +48,30 @@ namespace eCAL ///////////////////////////////////// // Constructor, Destructor, Create ///////////////////////////////////// - std::shared_ptr ClientSessionV0::create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + std::shared_ptr ClientSessionV0::create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { - std::shared_ptr instance(new ClientSessionV0(io_context, address, port, event_callback, logger)); + std::shared_ptr instance(new ClientSessionV0(io_context, server_list, event_callback, logger)); + + // Throw exception, if the server list is empty + if (server_list.empty()) + { + throw std::invalid_argument("Server list must not be empty"); + } - instance->resolve_endpoint(); + instance->resolve_endpoint(0); return instance; } - ClientSessionV0::ClientSessionV0(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + ClientSessionV0::ClientSessionV0(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) : ClientSessionBase(io_context, event_callback) - , address_ (address) - , port_ (port) + , server_list_ (server_list) , service_call_queue_strand_(*io_context) , resolver_ (*io_context) , logger_ (logger) @@ -78,21 +91,44 @@ namespace eCAL ////////////////////////////////////// // Connection establishement ////////////////////////////////////// - void ClientSessionV0::resolve_endpoint() + void ClientSessionV0::resolve_endpoint(size_t server_list_index) { - ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + address_ + ":" + std::to_string(port_) + "]..."); + ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + server_list_[server_list_index].first + ":" + std::to_string(server_list_[server_list_index].second) + "]..."); - const asio::ip::tcp::resolver::query query(address_, std::to_string(port_)); + const asio::ip::tcp::resolver::query query(server_list_[server_list_index].first, std::to_string(server_list_[server_list_index].second)); resolver_.async_resolve(query - , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this()] + , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this(), server_list_index] (asio::error_code ec, const asio::ip::tcp::resolver::iterator& resolved_endpoints) { if (ec) { - const std::string message = "Failed resolving endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); +#if ECAL_SERVICE_LOG_DEBUG_ENABLED + { + const std::string message = "Failed resolving endpoint [" + me->server_list_[server_list_index].first + ":" + std::to_string(me->server_list_[server_list_index].second) + "]: " + ec.message(); + ECAL_SERVICE_LOG_DEBUG(me->logger_, message); + } +#endif + + if (server_list_index + 1 < me->server_list_.size()) + { + // Try next possible endpoint + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed resolving any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -100,7 +136,7 @@ namespace eCAL #if ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED // Verbose-debug log of all endpoints { - std::string endpoints_str = "Resolved endpoints for " + me->address_ + ": "; + std::string endpoints_str = "Resolved endpoints for " + me->server_list_[server_list_index].first + ": "; for (auto it = resolved_endpoints; it != asio::ip::tcp::resolver::iterator(); ++it) { endpoints_str += endpoint_to_string(*it) + ", "; @@ -108,12 +144,12 @@ namespace eCAL ECAL_SERVICE_LOG_DEBUG_VERBOSE(me->logger_, endpoints_str); } #endif //ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED - me->connect_to_endpoint(resolved_endpoints); + me->connect_to_endpoint(resolved_endpoints, server_list_index); } })); } - void ClientSessionV0::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints) + void ClientSessionV0::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index) { // Convert the resolved_endpoints iterator to an endpoint sequence // (i.e. a vector of endpoints) @@ -126,14 +162,36 @@ namespace eCAL const std::lock_guard socket_lock(socket_mutex_); asio::async_connect(socket_ , *endpoint_sequence - , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) + , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence, server_list_index](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) { (void)endpoint; if (ec) { - const std::string message = "Failed to connect to endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); + { + // Log an error + const std::string message = "Failed to connect to endpoint [" + me->chosen_endpoint_.first + ":" + std::to_string(me->chosen_endpoint_.second) + "]: " + ec.message(); + me->logger_(LogLevel::Error, message); + } + + // If there are more servers available, try the next one + if (server_list_index + 1 < me->server_list_.size()) + { + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed to connect to any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -148,7 +206,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(me->socket_mutex_); - me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } if (socket_option_ec) { @@ -156,6 +214,12 @@ namespace eCAL } } + { + // Set the chosen endpoint + const std::lock_guard chosen_endpoint_lock(me->chosen_endpoint_mutex_); + me->chosen_endpoint_ = me->server_list_[server_list_index]; + } + const std::string message = "Connected to server. Using protocol version 0."; me->logger_(LogLevel::Info, "[" + get_connection_info_string(me->socket_) + "] " + message); @@ -354,14 +418,29 @@ namespace eCAL // Status API ////////////////////////////////////// - std::string ClientSessionV0::get_address() const + std::string ClientSessionV0::get_host() const { - return address_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.first; } std::uint16_t ClientSessionV0::get_port() const { - return port_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.second; + } + + asio::ip::tcp::endpoint ClientSessionV0::get_remote_endpoint() const + { + // form remote endpoint string + { + asio::error_code ec; + auto endpoint = socket_.remote_endpoint(ec); + if (!ec) + return endpoint; + else + return asio::ip::tcp::endpoint(); + } } State ClientSessionV0::get_state() const @@ -480,7 +559,7 @@ namespace eCAL { // Set the stopped_by_user_ flag to true, so that the async operations stop enqueuing new service calls. - std::lock_guard service_state_lock(service_state_mutex_); + const std::lock_guard service_state_lock(service_state_mutex_); stopped_by_user_ = true; } @@ -500,12 +579,12 @@ namespace eCAL { { asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> we already get the value by the ec parameter } { asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> we already get the value by the ec parameter } } } diff --git a/ecal/service/ecal_service/src/client_session_impl_v0.h b/ecal/service/ecal_service/src/client_session_impl_v0.h index 96017f1be3..326ad1b648 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v0.h +++ b/ecal/service/ecal_service/src/client_session_impl_v0.h @@ -20,13 +20,20 @@ #pragma once #include "client_session_impl_base.h" -#include -#include +#include +#include #include #include #include #include +#include +#include + +#include + +#include +#include namespace eCAL { @@ -50,18 +57,16 @@ namespace eCAL // Constructor, Destructor, Create ///////////////////////////////////// public: - static std::shared_ptr create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger = default_logger("Service Client V1")); + static std::shared_ptr create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger = default_logger("Service Client V1")); protected: - ClientSessionV0(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger); + ClientSessionV0(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger); public: // Delete copy / move constructor and assignment operator @@ -77,8 +82,8 @@ namespace eCAL // Connection establishement ////////////////////////////////////// private: - void resolve_endpoint(); - void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints); + void resolve_endpoint(size_t server_list_index); + void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index); ////////////////////////////////////// // Service calls @@ -94,8 +99,10 @@ namespace eCAL // Status API ////////////////////////////////////// public: - std::string get_address() const override; - std::uint16_t get_port() const override; + std::string get_host() const override; + std::uint16_t get_port() const override; + asio::ip::tcp::endpoint get_remote_endpoint() const override; + State get_state() const override; std::uint8_t get_accepted_protocol_version() const override; int get_queue_size() const override; @@ -137,8 +144,10 @@ namespace eCAL ////////////////////////////////////// private: - const std::string address_; //!< The original address that this client was created with. - const std::uint16_t port_; //!< The original port that this client was created with. + const std::vector> server_list_; //!< The list of servers that this client was created with. They will be tried in order. + + mutable std::mutex chosen_endpoint_mutex_; //!< Protects the chosen_endpoint_ variable. + std::pair chosen_endpoint_; //!< The endpoint that the client is currently connected to. Protected by chosen_endpoint_mutex_. asio::io_context::strand service_call_queue_strand_; asio::ip::tcp::resolver resolver_; diff --git a/ecal/service/ecal_service/src/client_session_impl_v1.cpp b/ecal/service/ecal_service/src/client_session_impl_v1.cpp index 25cd2e7a69..576a3f3c5b 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v1.cpp +++ b/ecal/service/ecal_service/src/client_session_impl_v1.cpp @@ -18,20 +18,35 @@ */ #include "client_session_impl_v1.h" +#include "client_session_impl_base.h" #include "protocol_v1.h" +#include "protocol_layout.h" #include "log_helpers.h" #include "log_defs.h" #include #include -#include #include #include +#include #include #include #include +#include + +#include +#include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + namespace eCAL { namespace service @@ -42,27 +57,30 @@ namespace eCAL ///////////////////////////////////// // Constructor, Destructor, Create ///////////////////////////////////// - std::shared_ptr ClientSessionV1::create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + std::shared_ptr ClientSessionV1::create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) { - std::shared_ptr instance(new ClientSessionV1(io_context, address, port, event_callback, logger)); + std::shared_ptr instance(new ClientSessionV1(io_context, server_list, event_callback, logger)); + + // Throw exception, if the server list is empty + if (server_list.empty()) + { + throw std::invalid_argument("Server list must not be empty"); + } - instance->resolve_endpoint(); + instance->resolve_endpoint(0); return instance; } - ClientSessionV1::ClientSessionV1(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger) + ClientSessionV1::ClientSessionV1(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger) : ClientSessionBase(io_context, event_callback) - , address_ (address) - , port_ (port) + , server_list_ (server_list) , service_call_queue_strand_(*io_context) , resolver_ (*io_context) , logger_ (logger) @@ -83,21 +101,44 @@ namespace eCAL ////////////////////////////////////// // Connection establishement ////////////////////////////////////// - void ClientSessionV1::resolve_endpoint() + void ClientSessionV1::resolve_endpoint(size_t server_list_index) { - ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + address_ + ":" + std::to_string(port_) + "]..."); + ECAL_SERVICE_LOG_DEBUG(logger_, "Resolving endpoint [" + server_list_[server_list_index].first + ":" + std::to_string(server_list_[server_list_index].second) + "]..."); - const asio::ip::tcp::resolver::query query(address_, std::to_string(port_)); + const asio::ip::tcp::resolver::query query(server_list_[server_list_index].first, std::to_string(server_list_[server_list_index].second)); resolver_.async_resolve(query - , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this()] + , service_call_queue_strand_.wrap([me = enable_shared_from_this::shared_from_this(), server_list_index] (asio::error_code ec, const asio::ip::tcp::resolver::iterator& resolved_endpoints) { if (ec) { - const std::string message = "Failed resolving endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); +#if ECAL_SERVICE_LOG_DEBUG_ENABLED + { + const std::string message = "Failed resolving endpoint [" + me->server_list_[server_list_index].first + ":" + std::to_string(me->server_list_[server_list_index].second) + "]: " + ec.message(); + ECAL_SERVICE_LOG_DEBUG(me->logger_, message); + } +#endif + + if (server_list_index + 1 < me->server_list_.size()) + { + // Try next possible endpoint + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed resolving any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -105,7 +146,7 @@ namespace eCAL #if ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED // Verbose-debug log of all endpoints { - std::string endpoints_str = "Resolved endpoints for " + me->address_ + ": "; + std::string endpoints_str = "Resolved endpoints for " + me->server_list_[server_list_index].first + ": "; for (auto it = resolved_endpoints; it != asio::ip::tcp::resolver::iterator(); ++it) { endpoints_str += endpoint_to_string(*it) + ", "; @@ -113,12 +154,12 @@ namespace eCAL ECAL_SERVICE_LOG_DEBUG_VERBOSE(me->logger_, endpoints_str); } #endif //ECAL_SERVICE_LOG_DEBUG_VERBOSE_ENABLED - me->connect_to_endpoint(resolved_endpoints); + me->connect_to_endpoint(resolved_endpoints, server_list_index); } })); } - void ClientSessionV1::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints) + void ClientSessionV1::connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index) { // Convert the resolved_endpoints iterator to an endpoint sequence // (i.e. a vector of endpoints) @@ -131,14 +172,36 @@ namespace eCAL const std::lock_guard socket_lock(socket_mutex_); asio::async_connect(socket_ , *endpoint_sequence - , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) + , service_call_queue_strand_.wrap([me = shared_from_this(), endpoint_sequence, server_list_index](asio::error_code ec, const asio::ip::tcp::endpoint& endpoint) { (void)endpoint; if (ec) { - const std::string message = "Failed to connect to endpoint [" + me->address_ + ":" + std::to_string(me->port_) + "]: " + ec.message(); - me->logger_(LogLevel::Error, message); - me->handle_connection_loss_error(message); + { + // Log an error + const std::string message = "Failed to connect to endpoint [" + me->chosen_endpoint_.first + ":" + std::to_string(me->chosen_endpoint_.second) + "]: " + ec.message(); + me->logger_(LogLevel::Error, message); + } + + // If there are more servers available, try the next one + if (server_list_index + 1 < me->server_list_.size()) + { + me->resolve_endpoint(server_list_index + 1); + } + else + { + std::string message = "Failed to connect to any endpoint: "; + for (size_t j = 0; j < me->server_list_.size(); ++j) + { + message += me->server_list_[j].first + ":" + std::to_string(me->server_list_[j].second); + if (j + 1 < me->server_list_.size()) + { + message += ", "; + } + } + me->logger_(LogLevel::Error, message); + me->handle_connection_loss_error(message); + } return; } else @@ -153,7 +216,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(me->socket_mutex_); - me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + me->socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } if (socket_option_ec) { @@ -161,6 +224,11 @@ namespace eCAL } } + { + const std::lock_guard chosen_endpoint_lock(me->chosen_endpoint_mutex_); + me->chosen_endpoint_ = me->server_list_[server_list_index]; + } + // Start sending the protocol handshake to the server. This will tell us the actual protocol version. me->send_protocol_handshake_request(); } @@ -477,14 +545,29 @@ namespace eCAL // Status API ////////////////////////////////////// - std::string ClientSessionV1::get_address() const + std::string ClientSessionV1::get_host() const { - return address_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.first; } std::uint16_t ClientSessionV1::get_port() const { - return port_; + const std::lock_guard chosen_endpoint_lock(chosen_endpoint_mutex_); + return chosen_endpoint_.second; + } + + asio::ip::tcp::endpoint ClientSessionV1::get_remote_endpoint() const + { + // form remote endpoint string + { + asio::error_code ec; + auto endpoint = socket_.remote_endpoint(ec); + if (!ec) + return endpoint; + else + return asio::ip::tcp::endpoint(); + } } State ClientSessionV1::get_state() const @@ -603,7 +686,7 @@ namespace eCAL { // Set the stopped_by_user_ flag to true, so that the async operations stop enqueuing new service calls. - std::lock_guard service_state_lock(service_state_mutex_); + const std::lock_guard service_state_lock(service_state_mutex_); stopped_by_user_ = true; } @@ -623,12 +706,12 @@ namespace eCAL { { asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> we already get the return value from the ec parameter } { asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> we already get the return value from the ec parameter } } } diff --git a/ecal/service/ecal_service/src/client_session_impl_v1.h b/ecal/service/ecal_service/src/client_session_impl_v1.h index ebd1a92529..c95d360f1e 100644 --- a/ecal/service/ecal_service/src/client_session_impl_v1.h +++ b/ecal/service/ecal_service/src/client_session_impl_v1.h @@ -20,14 +20,21 @@ #pragma once #include "client_session_impl_base.h" + #include +#include #include -#include - #include #include #include #include +#include +#include + +#include + +#include +#include namespace eCAL { @@ -51,18 +58,16 @@ namespace eCAL // Constructor, Destructor, Create ///////////////////////////////////// public: - static std::shared_ptr create(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger_ = default_logger("Service Client V1")); + static std::shared_ptr create(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger_ = default_logger("Service Client V1")); protected: - ClientSessionV1(const std::shared_ptr& io_context - , const std::string& address - , std::uint16_t port - , const EventCallbackT& event_callback - , const LoggerT& logger); + ClientSessionV1(const std::shared_ptr& io_context + , const std::vector>& server_list + , const EventCallbackT& event_callback + , const LoggerT& logger); public: // Delete copy / move constructor and assignment operator @@ -78,8 +83,8 @@ namespace eCAL // Connection establishement ////////////////////////////////////// private: - void resolve_endpoint(); - void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints); + void resolve_endpoint(size_t server_list_index); + void connect_to_endpoint(const asio::ip::tcp::resolver::iterator& resolved_endpoints, size_t server_list_index); void send_protocol_handshake_request(); void receive_protocol_handshake_response(); @@ -98,8 +103,10 @@ namespace eCAL // Status API ////////////////////////////////////// public: - std::string get_address() const override; - std::uint16_t get_port() const override; + std::string get_host() const override; + std::uint16_t get_port() const override; + asio::ip::tcp::endpoint get_remote_endpoint() const override; + State get_state() const override; std::uint8_t get_accepted_protocol_version() const override; int get_queue_size() const override; @@ -144,8 +151,10 @@ namespace eCAL static constexpr std::uint8_t MIN_SUPPORTED_PROTOCOL_VERSION = 1; static constexpr std::uint8_t MAX_SUPPORTED_PROTOCOL_VERSION = 1; - const std::string address_; //!< The original address that this client was created with. - const std::uint16_t port_; //!< The original port that this client was created with. + const std::vector> server_list_; //!< The list of servers that this client was created with. They will be tried in order. + + mutable std::mutex chosen_endpoint_mutex_; //!< Protects the chosen_endpoint_ variable. + std::pair chosen_endpoint_; //!< The endpoint that the client is currently connected to. Protected by chosen_endpoint_mutex_. asio::io_context::strand service_call_queue_strand_; asio::ip::tcp::resolver resolver_; diff --git a/ecal/service/ecal_service/src/log_defs.h b/ecal/service/ecal_service/src/log_defs.h index 1ca102e8c0..1b48d85905 100644 --- a/ecal/service/ecal_service/src/log_defs.h +++ b/ecal/service/ecal_service/src/log_defs.h @@ -20,8 +20,6 @@ #pragma once -#include - ///////////////////////////////////////////// // Enable / disable debug logging ///////////////////////////////////////////// diff --git a/ecal/service/ecal_service/src/protocol_v0.cpp b/ecal/service/ecal_service/src/protocol_v0.cpp index 54d231b4a4..c66d297e73 100644 --- a/ecal/service/ecal_service/src/protocol_v0.cpp +++ b/ecal/service/ecal_service/src/protocol_v0.cpp @@ -26,6 +26,15 @@ #include #include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/protocol_v1.cpp b/ecal/service/ecal_service/src/protocol_v1.cpp index 3ce1395957..7be282cbe7 100644 --- a/ecal/service/ecal_service/src/protocol_v1.cpp +++ b/ecal/service/ecal_service/src/protocol_v1.cpp @@ -27,6 +27,14 @@ #include #include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/server.cpp b/ecal/service/ecal_service/src/server.cpp index c5c4002f15..7e6c726d3c 100644 --- a/ecal/service/ecal_service/src/server.cpp +++ b/ecal/service/ecal_service/src/server.cpp @@ -17,12 +17,15 @@ * ========================= eCAL LICENSE ================================= */ -#include #include -#include +#include #include +#include + +#include + #include "server_impl.h" namespace eCAL diff --git a/ecal/service/ecal_service/src/server_impl.cpp b/ecal/service/ecal_service/src/server_impl.cpp index 28da90c43e..f1f077baee 100644 --- a/ecal/service/ecal_service/src/server_impl.cpp +++ b/ecal/service/ecal_service/src/server_impl.cpp @@ -24,9 +24,16 @@ #include #include +#include + +#include "server_session_impl_base.h" #include "server_session_impl_v1.h" #include "server_session_impl_v0.h" +#include +#include +#include + #include "log_defs.h" namespace eCAL @@ -137,7 +144,7 @@ namespace eCAL { const std::lock_guard acceptor_lock(acceptor_mutex_); - acceptor_.open(endpoint.protocol(), ec); + acceptor_.open(endpoint.protocol(), ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (ec) { @@ -152,7 +159,7 @@ namespace eCAL { asio::error_code ec; { - acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true), ec); + acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true), ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter const std::lock_guard acceptor_lock(acceptor_mutex_); } if (ec) @@ -168,7 +175,7 @@ namespace eCAL asio::error_code ec; { const std::lock_guard acceptor_lock(acceptor_mutex_); - acceptor_.bind(endpoint, ec); + acceptor_.bind(endpoint, ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (ec) { @@ -183,7 +190,7 @@ namespace eCAL asio::error_code ec; { const std::lock_guard acceptor_lock(acceptor_mutex_); - acceptor_.listen(asio::socket_base::max_listen_connections, ec); + acceptor_.listen(asio::socket_base::max_listen_connections, ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (ec) { @@ -296,7 +303,7 @@ namespace eCAL if (acceptor_.is_open()) { asio::error_code ec; - acceptor_.close(ec); + acceptor_.close(ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } } diff --git a/ecal/service/ecal_service/src/server_impl.h b/ecal/service/ecal_service/src/server_impl.h index 9b33edd76d..6ef2355d63 100644 --- a/ecal/service/ecal_service/src/server_impl.h +++ b/ecal/service/ecal_service/src/server_impl.h @@ -19,9 +19,7 @@ #pragma once -#include #include -#include #include #include #include @@ -32,7 +30,6 @@ #pragma warning(disable: 4834) #endif #include -#include #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/ecal/service/ecal_service/src/server_manager.cpp b/ecal/service/ecal_service/src/server_manager.cpp index 0b462412c9..2fad646d96 100644 --- a/ecal/service/ecal_service/src/server_manager.cpp +++ b/ecal/service/ecal_service/src/server_manager.cpp @@ -17,13 +17,19 @@ * ========================= eCAL LICENSE ================================= */ +#include + #include #include -#include #include #include #include +#include + +#include +#include + namespace eCAL { namespace service diff --git a/ecal/service/ecal_service/src/server_session_impl_base.h b/ecal/service/ecal_service/src/server_session_impl_base.h index 1d84acf6c2..ff663e28cf 100644 --- a/ecal/service/ecal_service/src/server_session_impl_base.h +++ b/ecal/service/ecal_service/src/server_session_impl_base.h @@ -23,18 +23,16 @@ #include #include -#include #ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4834) +#pragma warning(push) +#pragma warning(disable : 4834) #endif #include -#include #ifdef _MSC_VER - #pragma warning(pop) +#pragma warning(pop) #endif #include diff --git a/ecal/service/ecal_service/src/server_session_impl_v0.cpp b/ecal/service/ecal_service/src/server_session_impl_v0.cpp index d06dfe050c..a96f6ec8ac 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v0.cpp +++ b/ecal/service/ecal_service/src/server_session_impl_v0.cpp @@ -18,11 +18,12 @@ */ #include "server_session_impl_v0.h" +#include "server_session_impl_base.h" #include "log_helpers.h" #include "log_defs.h" - #include "protocol_layout.h" + #include #include #include @@ -30,6 +31,18 @@ #include #include +#include + +#include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + /////////////////////////////////////////////// // Create, Constructor, Destructor /////////////////////////////////////////////// @@ -104,7 +117,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(socket_mutex_); - socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } if (socket_option_ec) { @@ -131,13 +144,13 @@ namespace eCAL { // Shutdown the socket asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } { // Close the socket asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> We already get the return value rom the ec parameter } } } diff --git a/ecal/service/ecal_service/src/server_session_impl_v0.h b/ecal/service/ecal_service/src/server_session_impl_v0.h index 4c4f93dbe8..af3c33ea54 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v0.h +++ b/ecal/service/ecal_service/src/server_session_impl_v0.h @@ -22,12 +22,14 @@ #include "server_session_impl_base.h" #include #include +#include +#include + +#include + #include #include - #include -#include -#include namespace eCAL { diff --git a/ecal/service/ecal_service/src/server_session_impl_v1.cpp b/ecal/service/ecal_service/src/server_session_impl_v1.cpp index 3633a2445c..568e524aef 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v1.cpp +++ b/ecal/service/ecal_service/src/server_session_impl_v1.cpp @@ -18,11 +18,13 @@ */ #include "server_session_impl_v1.h" +#include "server_session_impl_base.h" #include "protocol_v1.h" - +#include "protocol_layout.h" #include "log_defs.h" #include "log_helpers.h" + #include #include #include @@ -30,6 +32,18 @@ #include #include +#include + +#include +#include +#include + +#ifdef WIN32 + #include +#else + #include +#endif + /////////////////////////////////////////////// // Create, Constructor, Destructor /////////////////////////////////////////////// @@ -88,7 +102,7 @@ namespace eCAL asio::error_code socket_option_ec; { const std::lock_guard socket_lock(socket_mutex_); - socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); + socket_.set_option(asio::ip::tcp::no_delay(true), socket_option_ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } if (socket_option_ec) { @@ -108,12 +122,12 @@ namespace eCAL { // Shutdown the socket asio::error_code ec; - socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } { // Close the socket asio::error_code ec; - socket_.close(ec); + socket_.close(ec); // NOLINT(bugprone-unused-return-value) -> We already get the value from the ec parameter } } } diff --git a/ecal/service/ecal_service/src/server_session_impl_v1.h b/ecal/service/ecal_service/src/server_session_impl_v1.h index d61e2ecc1e..4a233270bb 100644 --- a/ecal/service/ecal_service/src/server_session_impl_v1.h +++ b/ecal/service/ecal_service/src/server_session_impl_v1.h @@ -20,14 +20,17 @@ #pragma once #include "server_session_impl_base.h" + #include #include +#include +#include + +#include + #include #include - #include -#include -#include namespace eCAL { diff --git a/ecal/service/sample/CMakeLists.txt b/ecal/service/samples/sample_client/CMakeLists.txt similarity index 97% rename from ecal/service/sample/CMakeLists.txt rename to ecal/service/samples/sample_client/CMakeLists.txt index 0296bbc6e4..90c4cd4a67 100644 --- a/ecal/service/sample/CMakeLists.txt +++ b/ecal/service/samples/sample_client/CMakeLists.txt @@ -16,7 +16,7 @@ # # ========================= eCAL LICENSE ================================= -project(service_sample) +project(service_sample_client) find_package(Threads REQUIRED) diff --git a/ecal/service/samples/sample_client/src/main.cpp b/ecal/service/samples/sample_client/src/main.cpp new file mode 100644 index 0000000000..403f981104 --- /dev/null +++ b/ecal/service/samples/sample_client/src/main.cpp @@ -0,0 +1,160 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2023 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +void print_usage(const std::string& arg0) +{ + std::cout << "Usage: " << arg0 << " Host:Port [Host:Port ...] [--protocol-version ]" << std::endl; +} + +std::pair parse_server(const std::string& server) +{ + // Find the last ':' to split into host and port + auto pos = server.find_last_of(':'); + if (pos == std::string::npos) + { + throw std::runtime_error("Invalid server: " + server); + } + + return std::make_pair(server.substr(0, pos), static_cast(std::stoi(server.substr(pos + 1)))); +} + +int main(int argc, char** argv) +{ + // Convert command line arguments into vector + std::vector args; + args.reserve(argc); + for (int i = 0; i < argc; ++i) + { + args.emplace_back(argv[i]); + } + + std::vector> server_list; + std::uint8_t protocol_version = 1; + + // Parse command line arguments + for (size_t i = 1; i < args.size(); ++i) + { + if (args[i] == "-h" || args[i] == "--help") + { + print_usage(args[0]); + return 0; + } + else if (args[i] == "--protocol-version") + { + if (i + 1 < args.size()) + { + protocol_version = static_cast(std::stoi(args[i + 1])); + ++i; + } + else + { + print_usage(args[0]); + return 1; + } + } + else + { + try + { + server_list.push_back(parse_server(args[i])); + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + print_usage(args[0]); + return 1; + } + } + } + + // Fail if the server list is empty + if (server_list.empty()) + { + print_usage(args[0]); + return 1; + } + + // Create an io_context + auto io_context = std::make_shared(); + + // Create a client manager + auto client_manager = eCAL::service::ClientManager::create(io_context, eCAL::service::default_logger("Client", eCAL::service::LogLevel::DebugVerbose)); + + // Create and start an io_context thread. + // The io_context will be stopped, when the server_manager and client_manager are stopped. + std::thread io_context_thread([&io_context]() { io_context->run(); }); + + // Client Callback + // + // This callback will be called, when the service call is finished. + auto client_response_callback + = [](const eCAL::service::Error& error, const std::shared_ptr& response) -> void + { + if (error) + std::cerr << "Error calling service: " << error.ToString() << std::endl; + else + std::cout << "Received response: " << *response << std::endl; + }; + + // Event callbacks (empty) + auto client_event_callback = [](eCAL::service::ClientEventType /*event*/, const std::string& /*message*/) {}; + + // Create client + // The client will connect to the server on the given port. + auto client = client_manager->create_client(protocol_version, server_list, client_event_callback); + + // Call the service non-blocking. The response will be passed to the callback. + int counter = 1; + while(true) + { + const auto request = std::make_shared("Hello World " + std::to_string(counter)); + client->async_call_service(request, client_response_callback); + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + ++counter; + } + + std::cout << "Shutting down :)" << std::endl; + + // Use managers to stop servers and clients + client_manager->stop(); + + // Join the io_context thread + io_context_thread.join(); + +} diff --git a/ecal/service/samples/sample_server/CMakeLists.txt b/ecal/service/samples/sample_server/CMakeLists.txt new file mode 100644 index 0000000000..ffeca863a7 --- /dev/null +++ b/ecal/service/samples/sample_server/CMakeLists.txt @@ -0,0 +1,39 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2023 Continental Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ========================= eCAL LICENSE ================================= + +project(service_sample_server) + +find_package(Threads REQUIRED) + +set(sources + src/main.cpp +) + +add_executable(${PROJECT_NAME} ${sources}) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + ecal_service) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14) + +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER core/service) + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES + ${sources} +) diff --git a/ecal/service/samples/sample_server/src/main.cpp b/ecal/service/samples/sample_server/src/main.cpp new file mode 100644 index 0000000000..d1a7921d6b --- /dev/null +++ b/ecal/service/samples/sample_server/src/main.cpp @@ -0,0 +1,124 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2023 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +void print_usage(const std::string& arg0) +{ + std::cout << "Usage: " << arg0 << " [--port ] [--protocol-version ]" << std::endl; +} + +int main(int argc, char** argv) +{ + uint16_t port = 0; + std::uint8_t protocol_version = 1; + + // convert command line arguments into vector + std::vector args; + args.reserve(argc); + for (int i = 0; i < argc; ++i) + { + args.emplace_back(argv[i]); + } + + // parse command line arguments + for (size_t i = 1; i < args.size(); ++i) + { + if (args[i] == "-h" || args[i] == "--help") + { + print_usage(args[0]); + return 0; + } + else if (args[i] == "--port") + { + if (i + 1 < args.size()) + { + port = static_cast(std::stoi(args[i + 1])); + ++i; + } + else + { + print_usage(args[0]); + return 1; + } + } + else if (args[i] == "--protocol-version") + { + if (i + 1 < args.size()) + { + protocol_version = static_cast(std::stoi(args[i + 1])); + ++i; + } + else + { + print_usage(args[0]); + return 1; + } + } + else + { + print_usage(args[0]); + return 1; + } + } + + // Create an io_context + auto io_context = std::make_shared(); + + // Create a server manager + auto server_manager = eCAL::service::ServerManager::create(io_context, eCAL::service::default_logger("Server", eCAL::service::LogLevel::DebugVerbose)); + + // Server Service callback + // + // This callback will be called, when a client calls the service. + // It is responsible for filling the response object. + auto server_service_callback + = [](const std::shared_ptr& request, const std::shared_ptr& response) -> void + { + *response = "Response on \"" + *request + "\""; + }; + + // Event callbacks (empty) + auto server_event_callback = [](eCAL::service::ServerEventType /*event*/, const std::string& /*message*/) {}; + + // Create server + // If the port is 0, the server will choose a port automatically + auto server = server_manager->create_server(protocol_version, port, server_service_callback, true, server_event_callback); + + // Print server port + std::cout << "Started Service Server on port: " << server->get_port() << std::endl; + std::cout << "Using protocol version: " << std::to_string(protocol_version) << std::endl; + + // Start io_context in main thread + io_context->run(); + + // Use managers to stop server + server_manager->stop(); +} diff --git a/ecal/service/samples/sample_standalone/CMakeLists.txt b/ecal/service/samples/sample_standalone/CMakeLists.txt new file mode 100644 index 0000000000..0806b506a7 --- /dev/null +++ b/ecal/service/samples/sample_standalone/CMakeLists.txt @@ -0,0 +1,39 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2023 Continental Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ========================= eCAL LICENSE ================================= + +project(service_sample_standalone) + +find_package(Threads REQUIRED) + +set(sources + src/main.cpp +) + +add_executable(${PROJECT_NAME} ${sources}) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + ecal_service) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14) + +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER core/service) + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES + ${sources} +) diff --git a/ecal/service/sample/src/main.cpp b/ecal/service/samples/sample_standalone/src/main.cpp similarity index 91% rename from ecal/service/sample/src/main.cpp rename to ecal/service/samples/sample_standalone/src/main.cpp index be38fbca0f..482aaf4bce 100644 --- a/ecal/service/sample/src/main.cpp +++ b/ecal/service/samples/sample_standalone/src/main.cpp @@ -20,9 +20,17 @@ #include #include +#include +#include +#include + +#include #include +#include +#include #include -#include + +#include int main(int /*argc*/, char** /*argv*/) { @@ -69,7 +77,7 @@ int main(int /*argc*/, char** /*argv*/) // Create client // The client will connect to the server on the given port. - auto client = client_manager->create_client(1, "127.0.0.1", server->get_port(), client_event_callback); + auto client = client_manager->create_client(1, {{ "127.0.0.1", server->get_port() }}, client_event_callback); // Call the service non-blocking. The response will be passed to the callback. for (int i = 1; i <= 10; i++) diff --git a/ecal/service/test/src/ecal_tcp_service_test.cpp b/ecal/service/test/src/ecal_tcp_service_test.cpp index 67455b54d4..cd5abf2fcb 100644 --- a/ecal/service/test/src/ecal_tcp_service_test.cpp +++ b/ecal/service/test/src/ecal_tcp_service_test.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include // Should not be needed, when I use the server manager / client manager #include // Should not be needed, when I use the server manager / client manager @@ -59,8 +60,6 @@ eCAL::service::LoggerT critical_logger(const std::string& node_name) constexpr std::uint8_t min_protocol_version = 0; constexpr std::uint8_t max_protocol_version = 1; - - #if 1 TEST(ecal_service, RAII_TcpServiceServer) // NOLINT { @@ -135,7 +134,7 @@ TEST(ecal_service, RAII_TcpServiceClient) // NOLINT io_context->run(); }); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", 12345, client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", 12345 }}, client_event_callback); io_context->stop(); io_thread->join(); @@ -201,7 +200,7 @@ TEST(ecal_service, RAII_TcpServiceServerAndClient) // NOLINT EXPECT_EQ(tcp_server->get_connection_count(), 0); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", tcp_server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", tcp_server->get_port() }}, client_event_callback); tcp_client_weak = client_v1; client_v1->async_call_service(std::make_shared("Hello World"), client_slow_response_callback); @@ -283,7 +282,7 @@ TEST(ecal_service, RAII_StopDuringServiceCall) // NOLINT io_context->run(); }); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", tcp_server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", tcp_server->get_port() }}, client_event_callback); tcp_client_weak = client_v1; client_v1->async_call_service(std::make_shared("Hello World"), client_slow_response_callback); @@ -359,7 +358,7 @@ TEST(ecal_service, Communication_SlowCommunication) // NOLINT EXPECT_EQ(server->get_connection_count(), 0); } - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -470,7 +469,7 @@ TEST(ecal_service, CallbacksConnectDisconnect_ClientDisconnectsFirst) // NOLINT }; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -604,7 +603,7 @@ TEST(ecal_service, CommunicationAndCallbacks_ClientsDisconnectFirst) // NOLINT EXPECT_EQ(server->get_connection_count(), 0); } - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -777,7 +776,7 @@ TEST(ecal_service, CommunicationAndCallbacks_ServerDisconnectsFirst) // NOLINT }; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -946,7 +945,7 @@ TEST(ecal_service, CommunicationAndCallbacks_StressfulCommunication) // NOLINT num_client_event_callback_called++; }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // Directly run a bunch of clients and call each client a bunch of times @@ -1087,7 +1086,7 @@ TEST(ecal_service, CommunicationAndCallbacks_StressfulCommunicationNoParallelCal num_clients_connected++; } }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // wait for the clients to connect @@ -1234,7 +1233,7 @@ TEST(ecal_service, CommunicationAndCallbacks_StressfulCommunicationMassivePayloa num_client_event_callback_called++; }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // Directly run a bunch of clients and call each client a bunch of times @@ -1351,10 +1350,10 @@ TEST(ecal_service, Callback_ServerAndClientManagers) // NOLINT auto server1 = server_manager->create_server(protocol_version, 0, server_service_callback, true, increment_atomic_signalable(server1_event_callback_called)); auto server2 = server_manager->create_server(protocol_version, 0, server_service_callback, true, increment_atomic_signalable(server2_event_callback_called)); - auto client1_1 = client_manager->create_client(protocol_version, "127.0.0.1", server1->get_port(), increment_atomic_signalable(client1_1_event_callback_called)); - auto client1_2 = client_manager->create_client(protocol_version, "127.0.0.1", server1->get_port(), increment_atomic_signalable(client1_2_event_callback_called)); - auto client2_1 = client_manager->create_client(protocol_version, "127.0.0.1", server2->get_port(), increment_atomic_signalable(client2_1_event_callback_called)); - auto client2_2 = client_manager->create_client(protocol_version, "127.0.0.1", server2->get_port(), increment_atomic_signalable(client2_2_event_callback_called)); + auto client1_1 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server1->get_port() }}, increment_atomic_signalable(client1_1_event_callback_called)); + auto client1_2 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server1->get_port() }}, increment_atomic_signalable(client1_2_event_callback_called)); + auto client2_1 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server2->get_port() }}, increment_atomic_signalable(client2_1_event_callback_called)); + auto client2_2 = client_manager->create_client(protocol_version, {{ "127.0.0.1", server2->get_port() }}, increment_atomic_signalable(client2_2_event_callback_called)); // Wait for the clients to be connected client1_1_event_callback_called.wait_for([&](int value) { return value >= 1; }, std::chrono::seconds(5)); @@ -1474,7 +1473,7 @@ TEST(ecal_service, Callback_ServiceCallFromCallback) // NOLINT EXPECT_EQ(num_client_response_callback1_called, 0); EXPECT_EQ(num_client_response_callback2_called, 0); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); const eCAL::service::ClientSession::ResponseCallbackT response_callback = [&num_client_response_callback1_called, &num_client_response_callback2_called, client_v1] @@ -1565,7 +1564,7 @@ TEST(ecal_service, Callback_SerializedServiceCallbacks) // NOLINT clients.reserve(num_clients); for (int i = 0; i < num_clients; i++) { - clients.push_back(client_manager->create_client(protocol_version, "127.0.0.1", server->get_port(), client_event_callback)); + clients.push_back(client_manager->create_client(protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback)); } num_client_event_callback_called.wait_for([&num_clients](int value) -> bool { return value >= num_clients; }, std::chrono::milliseconds(500)); @@ -1607,7 +1606,7 @@ TEST(ecal_service, Callback_SerializedServiceCallbacks) // NOLINT #if 1 // Call different eCAL Service API functions from within the callbacks -TEST(ecal_service, Callback_ApiCallsFromCallbacks) +TEST(ecal_service, Callback_ApiCallsFromCallbacks) // NOLINT { for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) { @@ -1670,8 +1669,9 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) if(client) { // We just test if those functions can be called without crashing - auto address = client->get_address(); + auto address = client->get_host(); auto port = client->get_port(); + auto endpoint = client->get_remote_endpoint(); auto protocol_version = client->get_accepted_protocol_version(); auto queue_size = client->get_queue_size(); auto state = client->get_state(); @@ -1687,8 +1687,9 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) if (client) { // We just test if those functions can be called without crashing - auto address = client->get_address(); + auto address = client->get_host(); auto port = client->get_port(); + auto endpoint = client->get_remote_endpoint(); auto protocol_version = client->get_accepted_protocol_version(); auto queue_size = client->get_queue_size(); auto state = client->get_state(); @@ -1704,7 +1705,7 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) EXPECT_EQ(num_client_response_callback_called.get(), 0); EXPECT_EQ(num_client_event_callback_called.get(), 0); - client = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -1750,6 +1751,126 @@ TEST(ecal_service, Callback_ApiCallsFromCallbacks) } #endif +#if 1 +// Connect to a list of hosts of which the first one does not exist, so the next one is used and connected to +TEST(ecal_service, BackupHost) +{ + for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) + { + const auto io_context = std::make_shared(); + const asio::io_context::work dummy_work(*io_context); + + atomic_signalable num_server_service_callback_called(0); + atomic_signalable num_server_event_callback_called (0); + atomic_signalable num_client_response_callback_called(0); + atomic_signalable num_client_event_callback_called (0); + + const eCAL::service::Server::ServiceCallbackT server_service_callback + = [&num_server_service_callback_called](const std::shared_ptr& /*request*/, const std::shared_ptr& /*response*/) -> void + { + num_server_service_callback_called++; + }; + + const eCAL::service::Server::EventCallbackT server_event_callback + = [&num_server_event_callback_called](eCAL::service::ServerEventType event, const std::string& /*message*/) -> void + { + num_server_event_callback_called++; + }; + + const eCAL::service::ClientSession::EventCallbackT client_event_callback + = [&num_client_event_callback_called](eCAL::service::ClientEventType event, const std::string& /*message*/) -> void + { + num_client_event_callback_called++; + }; + + auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); + + EXPECT_EQ(num_server_service_callback_called, 0); + EXPECT_EQ(num_server_event_callback_called, 0); + EXPECT_EQ(num_client_response_callback_called, 0); + EXPECT_EQ(num_client_event_callback_called, 0); + + std::vector> server_list = + { + { "NonExistingEndpoint", 123 }, // This endpoint cannot be resolved + { "127.0.0.1", 123 }, // This endpoint can be resolved, but the port is wrong + { "127.0.0.1", server->get_port() } // This endpoint is the correct one and will be tried last + }; + + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, server_list, client_event_callback); + + std::thread io_thread([&io_context]() + { + io_context->run(); + }); + + // Wait for the connected events to be called + num_server_event_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(10000)); // Going through the wrong endpoints may take some time + num_client_event_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(10000)); // Going through the wrong endpoints may take some time + + EXPECT_EQ(num_server_service_callback_called, 0); + EXPECT_EQ(num_server_event_callback_called, 1); + EXPECT_EQ(num_client_response_callback_called, 0); + EXPECT_EQ(num_client_event_callback_called, 1); + + // Check what host the client is connected to + auto connected_host = client->get_host(); + auto connected_port = client->get_port(); + auto endpoint = client->get_remote_endpoint(); + EXPECT_EQ(connected_host, "127.0.0.1"); + EXPECT_EQ(connected_port, server->get_port()); + EXPECT_EQ(endpoint.port(), server->get_port()); + EXPECT_EQ(endpoint.address().to_string(), "127.0.0.1"); + + // Call service and wait for the response + const eCAL::service::ClientSession::ResponseCallbackT response_callback + = [&num_client_response_callback_called](const eCAL::service::Error& error, const std::shared_ptr& /*response*/) -> void + { + EXPECT_FALSE(bool(error)); + num_client_response_callback_called++; + }; + + client->async_call_service(std::make_shared("Hello World"), response_callback); + + num_client_response_callback_called.wait_for([](int value) { return value >= 1; }, std::chrono::milliseconds(500)); + + EXPECT_EQ(num_server_service_callback_called, 1); + EXPECT_EQ(num_server_event_callback_called, 1); + EXPECT_EQ(num_client_response_callback_called, 1); + EXPECT_EQ(num_client_event_callback_called, 1); + + // Shutdown + server = nullptr; + client = nullptr; + + // join the io_thread + io_context->stop(); + io_thread.join(); + } +} +#endif + +#if 1 +// Test that the client create throws an exception when bein created with an empty server list +TEST(ecal_service, EmptyServerList) +{ + // Regular client + for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) + { + const auto io_context = std::make_shared(); + EXPECT_THROW(eCAL::service::ClientSession::create(io_context, protocol_version, {}, [](eCAL::service::ClientEventType, const std::string&) -> void {}), std::invalid_argument); + } + + // Client manager + for (std::uint8_t protocol_version = min_protocol_version; protocol_version <= max_protocol_version; protocol_version++) + { + const auto io_context = std::make_shared(); + auto client_manager = eCAL::service::ClientManager::create(io_context); + EXPECT_THROW(client_manager->create_client(protocol_version, {}, [](eCAL::service::ClientEventType, const std::string&) -> void {}), std::invalid_argument); + } +} +#endif + #if 1 TEST(ecal_service, ErrorCallback_ErrorCallbackNoServer) // NOLINT { @@ -1779,7 +1900,7 @@ TEST(ecal_service, ErrorCallback_ErrorCallbackNoServer) // NOLINT EXPECT_EQ(num_client_response_callback_called, 0); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, "NonExistingEndpoint", 12345, client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "NonExistingEndpoint", 12345 }}, client_event_callback); // Run the io_service std::thread io_thread([&io_context]() @@ -1854,7 +1975,7 @@ TEST(ecal_service, ErrorCallback_ErrorCallbackServerHasDisconnected) // NOLINT }; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2001,7 +2122,7 @@ TEST(ecal_service, ErrorCallback_ErrorCallbackClientDisconnects) // NOLINT {}; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback); + auto client_v1 = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2156,7 +2277,7 @@ TEST(ecal_service, ErrorCallback_StressfulErrorsHalfwayThrough) // NOLINT num_client_event_callback_called++; }; - client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version,"127.0.0.1", server->get_port(), client_event_callback, critical_logger("Client " + std::to_string(c)))); + client_list.push_back(eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback, critical_logger("Client " + std::to_string(c)))); } // Directly run a bunch of clients and call each client a bunch of times @@ -2340,7 +2461,7 @@ TEST(ecal_service, ErrorCallback_StressfulErrorsHalfwayThroughWithManagers) // N num_client_event_callback_called++; }; - client_list.push_back(client_manager->create_client(protocol_version,"127.0.0.1", server->get_port(), client_event_callback)); + client_list.push_back(client_manager->create_client(protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback)); } // Directly run a bunch of clients and call each client a bunch of times @@ -2466,7 +2587,7 @@ TEST(ecal_service, BlockingCall_RegularBlockingCall) // NOLINT {}; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2538,7 +2659,7 @@ TEST(ecal_service, BlockingCall_BlockingCallWithErrorHalfwayThrough) // NOLINT {}; auto server = eCAL::service::Server::create(io_context, protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = eCAL::service::ClientSession::create(io_context, protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + auto client = eCAL::service::ClientSession::create(io_context, protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2671,7 +2792,7 @@ TEST(ecal_service, BlockingCall_Stopped) // NOLINT // This test shows the prope {}; auto server = server_manager->create_server(protocol_version, 0, server_service_callback, true, server_event_callback); - auto client = client_manager->create_client(protocol_version, "127.0.0.1", server->get_port(), client_event_callback); + auto client = client_manager->create_client(protocol_version, {{ "127.0.0.1", server->get_port() }}, client_event_callback); std::thread io_thread([&io_context]() { @@ -2739,4 +2860,4 @@ TEST(ecal_service, BlockingCall_Stopped) // NOLINT // This test shows the prope } } } -#endif \ No newline at end of file +#endif