diff --git a/NetworkingLib/CMakeLists.txt b/NetworkingLib/CMakeLists.txt index 3cc3e61d48c94e48cb1953b60c6cc6e8d8c1b14f..9ca3ec979bd9b16343077dd440071cce2779b8f9 100644 --- a/NetworkingLib/CMakeLists.txt +++ b/NetworkingLib/CMakeLists.txt @@ -146,7 +146,7 @@ set(TEST_SOURCE_FILES test/PlatoonMessage.h test/PlatoonService.h test/Main.cpp - test/TestUtils.h include/Utils.h include/Error.h include/Socket.h include/Closeable.h) + test/TestUtils.h include/Utils.h include/Error.h include/Socket.h include/Closeable.h include/Resolver.h) add_executable(Test ${TEST_SOURCE_FILES}) target_link_libraries(Test ${Boost_LIBRARIES}) diff --git a/NetworkingLib/include/Closeable.h b/NetworkingLib/include/Closeable.h index 71ad57d21fbd7965ba41b455366ddc840dedbfab..691438a81bcd94b4a62c69608d28bab8be7d9a91 100644 --- a/NetworkingLib/include/Closeable.h +++ b/NetworkingLib/include/Closeable.h @@ -18,22 +18,63 @@ template<typename Closeable> class Closer { public: - Closer(Closeable & stream) : stream(stream) + Closer(Closeable & closeable) : closeable(closeable) {} ~Closer() { - close(stream); + close(closeable); } - static void close(Closeable & socket) + static void close(Closeable & closeable) { boost::system::error_code ignoredError; - socket.close(ignoredError); + closeable.close(ignoredError); } private: - Closeable & stream; + Closeable & closeable; +}; + +template<typename Closeable> +class Closer<std::shared_ptr<Closeable>> +{ +public: + Closer(std::shared_ptr<Closeable> & closeable) : closeable(closeable) + {} + + ~Closer() + { + close(closeable); + } + + static void close(std::shared_ptr<Closeable> & closeable) + { + boost::system::error_code ignoredError; + closeable->close(ignoredError); + } + +private: + std::shared_ptr<Closeable> & closeable; +}; + + +template<typename Closeable> +struct IsOpen +{ + bool operator()(Closeable & closeable) const + { + return closeable.is_open(); + } +}; + +template<typename Closeable> +struct IsOpen<std::shared_ptr<Closeable>> +{ + bool operator()(std::shared_ptr<Closeable> & closeable) const + { + return closeable->is_open(); + } }; template<typename AsyncOperation, typename... AsyncOperationArgs, typename Closeable> @@ -60,21 +101,21 @@ void timedOperation(Networking & net, // Run asynchronous connect. asyncOperation( std::forward<AsyncOperationArgs>(args)..., - [&closeableError, timer](const boost::system::error_code & error, auto ...) + [&closeableError, timer](const boost::system::error_code & error, auto && ... handlerArgs) { timer->stop(); // update closeableError variable closeableError = error; }); - // Wait until "something happens" with the socket. + // Wait until "something happens" with the closeable. net.waitUntil([&closeableError]() { return closeableError != boost::asio::error::would_block; }); // Determine whether a connection was successfully established. // Even though our timer handler might have run to close the closeable, the connect operation // might have notionally succeeded! - if (closeableError || !closeable.is_open()) + if (closeableError || !IsOpen<Closeable>{}(closeable)) { if (closeableError == boost::asio::error::operation_aborted) throw error::Aborted{}; @@ -110,7 +151,7 @@ void timedAsyncOperation(Networking & net, timer->stop(); auto errorCode = error::codes::SUCCESS; - if (!closeable.is_open()) + if (!IsOpen<Closeable>{}(closeable)) errorCode = error::codes::ABORTED; else if (opError) errorCode = error::codes::FAILED_OPERATION; diff --git a/NetworkingLib/include/Resolver.h b/NetworkingLib/include/Resolver.h new file mode 100644 index 0000000000000000000000000000000000000000..30dc3c986424b41217d37787d66dc6fd6760ee71 --- /dev/null +++ b/NetworkingLib/include/Resolver.h @@ -0,0 +1,77 @@ +// +// Created by philipp on 12.01.18. +// + +#ifndef NETWORKINGLIB_RESOLVER_H +#define NETWORKINGLIB_RESOLVER_H + +#include <memory> +#include <boost/asio/ip/tcp.hpp> +#include "Networking.h" +#include "Error.h" +#include "Utils.h" + +namespace networking +{ + +namespace internal +{ + +// This class is used for internal purposes. Please use the 'Resolver' class. +// The boost::asio resolvers do not feature a close mechanism which we need in order to perform operations with timeouts. +template<typename Protocol> +class CloseableResolver +{ +public: + using Resolver = typename Protocol::resolver; + using Query = typename Resolver::query; + using Iterator = typename Resolver::iterator; + + using ResolveHandler = std::function<void(const boost::system::error_code & error)>; + + CloseableResolver(boost::asio::io_service & ioService) + : resolver(ioService) + {} + + void async_resolve(const Query & query, + Iterator & endpointIterator, + ResolveHandler handler) + { + resolver.async_resolve( + query, + [&endpointIterator, handler](const auto & error, auto iterator) + { + endpointIterator = iterator; + handler(error); + }); + } + + void open() + { + opened = true; + } + + bool is_open() const noexcept + { + return opened; + } + + void close(boost::system::error_code &) + { + if (!opened) + return; + + opened = false; + resolver.cancel(); + } + +private: + std::atomic<bool> opened{true}; + Resolver resolver; +}; + +} + +} + +#endif //NETWORKINGLIB_RESOLVER_H diff --git a/NetworkingLib/include/ServiceClient.h b/NetworkingLib/include/ServiceClient.h index 3c7edfe21100e9533864373506900524e9062796..c55af6e27d5db03465304b0299dbff18fb6a9ef6 100644 --- a/NetworkingLib/include/ServiceClient.h +++ b/NetworkingLib/include/ServiceClient.h @@ -102,7 +102,7 @@ public: // Connect to server. networking::socket::asyncConnect( net, socket, host, port, state->timeout, - [self, state, request](const auto & error, auto ...) + [self, state, request](const auto & error, auto && ... rest) { // Only execute the handler if we're running on the networking thread. state->shouldCallHandler = true; diff --git a/NetworkingLib/include/Socket.h b/NetworkingLib/include/Socket.h index fbc3a4a68e8c311b40e14fbadea59c0fb332a326..ee27e258722cc9db79c4f5afb7e085f22b797985 100644 --- a/NetworkingLib/include/Socket.h +++ b/NetworkingLib/include/Socket.h @@ -6,6 +6,7 @@ #define NETWORKINGLIB_SOCKETOPS_H #include "Stream.h" +#include "Resolver.h" namespace networking { @@ -17,15 +18,28 @@ void connect(Networking & net, SocketService & socket, const std::string & host, std::uint16_t port, - const time::Duration & timeout) + time::Duration timeout) { - boost::asio::ip::tcp::resolver resolver(net.getIoService()); - auto endpointIterator = resolver.resolve({host, std::to_string(port)}); - // This lambda is used because we cannot pass boost::asio::async_connect directly as a function - // since it is a template. - auto asyncOperation = [](auto && ... args) + using Resolver = internal::CloseableResolver<boost::asio::ip::tcp>; + + auto startTime = time::now(); + + // Resolve host. + Resolver resolver{net.getIoService()}; + Resolver::Query query{host, std::to_string(port)}; + Resolver::Iterator endpointIterator; + + auto resolveOperation = [&resolver](auto && ... args) + { resolver.async_resolve(std::forward<decltype(args)>(args)...); }; + closeable::timedOperation(net, resolveOperation, resolver, timeout, query, endpointIterator); + + // Update timeout. + auto timeSpend = time::now() - startTime; + timeout -= timeSpend; + + auto connectOperation = [](auto && ... args) { boost::asio::async_connect(std::forward<decltype(args)>(args)...); }; - closeable::timedOperation(net, asyncOperation, socket, timeout, socket, endpointIterator); + closeable::timedOperation(net, connectOperation, socket, timeout, socket, endpointIterator); } template<typename SocketService, typename ConnectHandler> @@ -34,13 +48,40 @@ void asyncConnect(Networking & net, const std::string & host, std::uint16_t port, const time::Duration & timeout, - ConnectHandler handler) + const ConnectHandler & handler) { - boost::asio::ip::tcp::resolver resolver(net.getIoService()); - auto endpointIterator = resolver.resolve({host, std::to_string(port)}); - auto asyncOperation = [](auto && ... args) - { boost::asio::async_connect(std::forward<decltype(args)>(args)...); }; - closeable::timedAsyncOperation(net, asyncOperation, socket, timeout, handler, socket, endpointIterator); + using Resolver = internal::CloseableResolver<boost::asio::ip::tcp>; + + auto startTime = time::now(); + + // Resolve host. + auto resolver = std::make_shared<Resolver>(net.getIoService()); + Resolver::Query query{host, std::to_string(port)}; + auto endpointIterator = std::make_shared<Resolver::Iterator>(); + + auto resolveOperation = [resolver](auto && ... args) + { resolver->async_resolve(std::forward<decltype(args)>(args)...); }; + + closeable::timedAsyncOperation( + net, resolveOperation, resolver, timeout, + [&net, &socket, host, port, timeout, handler, resolver, endpointIterator, startTime] + (const auto & error) + { + if (error) + { + handler(error); + return; + } + + // Update timeout. + auto timeSpend = time::now() - startTime; + auto newTimeout = timeout - timeSpend; + + auto connectOperation = [](auto && ... args) + { boost::asio::async_connect(std::forward<decltype(args)>(args)...); }; + closeable::timedAsyncOperation(net, connectOperation, socket, newTimeout, handler, socket, *endpointIterator); + }, + query, *endpointIterator); } template< diff --git a/NetworkingLib/test/Main.cpp b/NetworkingLib/test/Main.cpp index d8b079b5324679d595f8a52d6a2b48e3e79f6a19..cc73322eb88ee7f5dc6535ad438f309f2b4325f1 100644 --- a/NetworkingLib/test/Main.cpp +++ b/NetworkingLib/test/Main.cpp @@ -8,18 +8,18 @@ int main(int argc, char ** argv) { - std::cout << "\ntestService\n\n"; - networking::test::testServices(); - std::cout <<"\ntestTcpClientTimeout\n\n"; - networking::test::testTcpClientTimeout(); - std::cout <<"\ntestMultipleConnections\n\n"; - networking::test::testMultipleConnections(); - std::cout << "\ntestStoppingServiceServer\n\n"; - networking::test::testStoppingServiceServer(); - std::cout << "\ntestAsyncDatagramReceiver\n\n"; - networking::test::testAsyncDatagramReceiver(); - std::cout << "\ntestPeriodicTimer\n\n"; - networking::test::testPeriodicTimer(); +// std::cout << "\ntestService\n\n"; +// networking::test::testServices(); +// std::cout <<"\ntestTcpClientTimeout\n\n"; +// networking::test::testTcpClientTimeout(); +// std::cout <<"\ntestMultipleConnections\n\n"; +// networking::test::testMultipleConnections(); +// std::cout << "\ntestStoppingServiceServer\n\n"; +// networking::test::testStoppingServiceServer(); +// std::cout << "\ntestAsyncDatagramReceiver\n\n"; +// networking::test::testAsyncDatagramReceiver(); +// std::cout << "\ntestPeriodicTimer\n\n"; +// networking::test::testPeriodicTimer(); std::cout << "\ntestServiceClientAsyncCallTimeout\n\n"; networking::test::testServiceClientAsyncCallTimeout(); std::cout << "\ntestDatagramSenderAsyncSend\n\n";