Skip to content

Instantly share code, notes, and snippets.

@hnrck
Created September 27, 2020 16:44
Show Gist options
  • Save hnrck/9a0970e7f57d8d33c4d08f584c242c5f to your computer and use it in GitHub Desktop.
Save hnrck/9a0970e7f57d8d33c4d08f584c242c5f to your computer and use it in GitHub Desktop.
Header only implementation of echo service in TCP with ASIO in C++

Echo service

Header only implementation of echo service in TCP with ASIO in C++

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)
/// @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);
}
/// @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
/// @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