Header only implementation of echo service in TCP with ASIO in C++
Created
September 27, 2020 16:44
-
-
Save hnrck/9a0970e7f57d8d33c4d08f584c242c5f to your computer and use it in GitHub Desktop.
Header only implementation of echo service in TCP with ASIO in C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cmake_minimum_required(VERSION 3.17) | |
project(echo_service) | |
if (APPLE) | |
set(CMAKE_MACOSX_RPATH ON) | |
endif () | |
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") | |
find_package(asio REQUIRED) | |
include_directories(${INCLUDE_DIR} SYSTEM ${asio_INCLUDE}) | |
set(CMAKE_CXX_STANDARD 20) | |
include_directories(${PROJECT_SOURCE_DIR}) | |
add_executable(echo_service service.h server.h main.cpp) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @file main.cpp | |
/// @author Henrick Deschamps | |
/// | |
/// @brief Main file | |
#include <cstdlib> | |
#include <memory> | |
#include <thread> | |
#include <server.h> | |
#include <service.h> | |
int main() { | |
const auto tcp_port = 55555U; | |
auto echo_service_thread = std::thread( | |
*(std::make_unique<tcp_server>( | |
std::make_shared<tcp_echo_service<>>(), | |
tcp_port) | |
) | |
); | |
echo_service_thread.join(); | |
return (EXIT_SUCCESS); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @file server.h | |
/// @author Henrick Deschamps | |
/// | |
/// @brief header only server interface and echo server implementation | |
#ifndef ECHO_SERVICE_SERVER_H | |
#define ECHO_SERVICE_SERVER_H | |
#include <thread> | |
#include <chrono> | |
#include <asio/awaitable.hpp> | |
#include <asio/co_spawn.hpp> | |
#include <asio/detached.hpp> | |
#include <asio/ip/tcp.hpp> | |
#include <asio/signal_set.hpp> | |
#include <service.h> | |
/// @class Server interface | |
struct server { | |
/// @fn Server destructor | |
/// @brief Default virtual destructor for inheritance | |
virtual ~server() = default; | |
/// @fn Call operator | |
/// @brief Call operator interface | |
virtual auto operator()() -> void = 0; | |
}; | |
/// @class TCP Server | |
/// @extends server | |
class tcp_server final : public server { | |
unsigned short int m_port; ///< @var Port | |
FILE *m_p_file_out; ///< @var Out file pointer | |
FILE *m_p_file_err; ///< @var Error file pointer | |
sp_service m_sp_service; ///< @var Service shared pointer | |
/// @fn Listener function | |
auto listener() -> asio::awaitable<void>; | |
public: | |
/// @fn TCP server | |
/// @brief TCP server constructor | |
/// @param sp_service Shared pointer to the service to serve | |
/// @param port Port value | |
/// @param p_file_out Pointer to file for output | |
/// @param p_file_err Pointer to file for errors | |
tcp_server(sp_service sp_service, unsigned short int port, FILE *p_file_out = stdout, FILE *p_file_err = stderr) : | |
m_sp_service(std::move(sp_service)), m_port{port}, m_p_file_out{p_file_out}, m_p_file_err{p_file_err} {} | |
/// @fn Call operator | |
/// @brief Call operator implementation | |
auto operator()() -> void final; | |
}; | |
auto tcp_server::listener() -> asio::awaitable<void> { | |
const auto executor = co_await asio::this_coro::executor; | |
auto acceptor = asio::ip::tcp::acceptor{executor, {asio::ip::tcp::v4(), m_port}}; | |
try { | |
for (;;) { | |
auto socket = co_await acceptor.async_accept(asio::use_awaitable); | |
asio::co_spawn(executor, (*m_sp_service)(std::move(socket)), asio::detached); | |
} | |
} catch (std::exception &e) { | |
std::fprintf(m_p_file_err, "Exception: %s\n", e.what()); | |
std::this_thread::sleep_for(std::chrono::seconds(1)); | |
} | |
} | |
auto tcp_server::operator()() -> void { | |
try { | |
auto io_context = asio::io_context{1}; | |
auto signals = asio::signal_set{io_context, SIGINT, SIGTERM}; | |
signals.async_wait([&](auto, auto) { io_context.stop(); }); | |
asio::co_spawn(io_context, listener(), asio::detached); | |
io_context.run(); | |
} catch (std::exception &e) { | |
std::fprintf(m_p_file_err, "Exception: %s\n", e.what()); | |
std::this_thread::sleep_for(std::chrono::seconds(1)); | |
} | |
} | |
#endif // ECHO_SERVICE_SERVER_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @file service.h | |
/// @author Henrick Deschamps | |
/// | |
/// @brief header only service interface and echo service implementation | |
#ifndef ECHO_SERVICE_SERVICE_H | |
#define ECHO_SERVICE_SERVICE_H | |
#include <memory> | |
#include <thread> | |
#include <chrono> | |
#include <asio/awaitable.hpp> | |
#include <asio/co_spawn.hpp> | |
#include <asio/ip/tcp.hpp> | |
#include <asio/write.hpp> | |
/// @var default buffer size for echo service | |
constexpr auto DEFAULT_BUFFER_SIZE = 1024U; | |
/// @typedef Service shared pointer | |
using sp_service = std::shared_ptr<struct service>; | |
/// @class Service interface | |
struct service { | |
/// @fn Service destructor | |
/// @brief Service destructor for inheritance | |
virtual ~service() = default; | |
/// @fn Call operator | |
/// @brief Call operator interface | |
/// @param socket Service TCP socket | |
virtual auto operator()(asio::ip::tcp::socket socket) -> asio::awaitable<void> = 0; | |
}; | |
/// @class TCP echo service | |
/// @extends service | |
/// @tparam BUFFER_SIZE echo service receive buffer size | |
template<unsigned int BUFFER_SIZE = DEFAULT_BUFFER_SIZE> | |
class tcp_echo_service final : public service { | |
private: | |
FILE *m_p_file_err; ///< @var Errors file pointer | |
public: | |
/// @fn TCP echo service | |
/// @brief TCP echo service constructor | |
/// @param p_file_err Errors file pointer | |
explicit tcp_echo_service(FILE *p_file_err = stderr) : m_p_file_err{p_file_err} {} | |
/// @fn Call operator | |
/// @brief Call operator implementation | |
/// @param socket Service TCP socket | |
auto operator()(asio::ip::tcp::socket socket) -> asio::awaitable<void> final; | |
}; | |
template<unsigned int BUFFER_SIZE> | |
auto tcp_echo_service<BUFFER_SIZE>::operator()(asio::ip::tcp::socket socket) -> asio::awaitable<void> { | |
try { | |
auto buffer = std::array<unsigned char, BUFFER_SIZE>{}; | |
const auto data = asio::mutable_buffer{buffer.data(), BUFFER_SIZE}; | |
for (;;) { | |
const auto n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable); | |
co_await asio::async_write(socket, asio::buffer(data, n), asio::use_awaitable); | |
} | |
} catch (std::exception &e) { | |
std::fprintf(m_p_file_err, "Exception: %s\n", e.what()); | |
std::this_thread::sleep_for(std::chrono::seconds(1)); | |
} | |
} | |
#endif // ECHO_SERVICE_SERVICE_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment