feat(common): introduce common networking and CLI infrastructure
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
// --- Error handling ---
|
||||
using ErrorCode = boost::system::error_code;
|
||||
|
||||
// --- IO Context ---
|
||||
using IoContext = boost::asio::io_context;
|
||||
|
||||
// --- TCP ---
|
||||
using TcpSocket = boost::asio::ip::tcp::socket;
|
||||
using TcpAcceptor = boost::asio::ip::tcp::acceptor;
|
||||
using TcpEndpoint = boost::asio::ip::tcp::endpoint;
|
||||
|
||||
// --- UDP (для DNS) ---
|
||||
using UdpSocket = boost::asio::ip::udp::socket;
|
||||
using UdpEndpoint = boost::asio::ip::udp::endpoint;
|
||||
|
||||
// --- Таймеры ---
|
||||
using SteadyTimer = boost::asio::steady_timer;
|
||||
|
||||
// --- Program options ---
|
||||
using OptionsDescription = boost::program_options::options_description;
|
||||
using VariablesMap = boost::program_options::variables_map;
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "cli_base.hpp"
|
||||
#include <boost/program_options/parsers.hpp>
|
||||
#include <iostream>
|
||||
|
||||
CliBase::CliBase(const std::string &title)
|
||||
: desc_(title)
|
||||
, help_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void CliBase::parse(int argc, char *argv[])
|
||||
{
|
||||
using namespace boost::program_options;
|
||||
|
||||
setupOptions(); // must be first
|
||||
|
||||
parsed_options parsed = parse_command_line(argc, argv, desc_);
|
||||
store(parsed, vm_);
|
||||
notify(vm_);
|
||||
|
||||
if (vm_.count("help")) {
|
||||
help_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CliBase::isHelp() const
|
||||
{
|
||||
return help_;
|
||||
}
|
||||
|
||||
void CliBase::printHelp() const
|
||||
{
|
||||
std::cout << desc_ << "\n";
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/program_options/options_description.hpp>
|
||||
#include <boost/program_options/variables_map.hpp>
|
||||
#include <string>
|
||||
|
||||
class CliBase
|
||||
{
|
||||
public:
|
||||
CliBase(const std::string &title);
|
||||
|
||||
virtual ~CliBase() = default;
|
||||
|
||||
// parse argc/argv
|
||||
void parse(int argc, char *argv[]);
|
||||
|
||||
bool isHelp() const;
|
||||
void printHelp() const;
|
||||
|
||||
protected:
|
||||
// children add their specific options here
|
||||
virtual void setupOptions() = 0;
|
||||
|
||||
boost::program_options::options_description desc_;
|
||||
boost::program_options::variables_map vm_;
|
||||
bool help_;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "logger.hpp"
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_io.hpp>
|
||||
|
||||
void Logger::init(bool toConsole, const std::string_view filePath)
|
||||
{
|
||||
using namespace boost::log;
|
||||
using namespace boost::posix_time;
|
||||
|
||||
add_common_attributes();
|
||||
|
||||
if (toConsole) {
|
||||
add_console_log(
|
||||
std::clog,
|
||||
keywords::format =
|
||||
(expressions::stream
|
||||
<< "["
|
||||
<< expressions::attr<boost::posix_time::ptime>("TimeStamp")
|
||||
<< "] "
|
||||
<< "<" << trivial::severity << "> " << expressions::smessage)
|
||||
);
|
||||
}
|
||||
|
||||
if (!filePath.empty()) {
|
||||
add_file_log(
|
||||
keywords::file_name = filePath,
|
||||
keywords::auto_flush = true,
|
||||
keywords::format =
|
||||
(expressions::stream
|
||||
<< "["
|
||||
<< expressions::attr<boost::posix_time::ptime>("TimeStamp")
|
||||
<< "] "
|
||||
<< "<" << trivial::severity << "> " << expressions::smessage)
|
||||
);
|
||||
}
|
||||
|
||||
core::get()->set_filter(trivial::severity >= trivial::info);
|
||||
}
|
||||
|
||||
void Logger::info(const std::string_view msg)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << msg;
|
||||
}
|
||||
|
||||
void Logger::warn(const std::string_view msg)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << msg;
|
||||
}
|
||||
|
||||
void Logger::error(const std::string_view msg)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
}
|
||||
|
||||
void Logger::debug(const std::string_view msg)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << msg;
|
||||
}
|
||||
|
||||
void Logger::fatal(const std::string_view msg)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(fatal) << msg;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||
#include <boost/log/utility/setup/console.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
static void
|
||||
init(bool toConsole = true, const std::string_view filePath = "");
|
||||
|
||||
static void info(const std::string_view msg);
|
||||
static void warn(const std::string_view msg);
|
||||
static void error(const std::string_view msg);
|
||||
static void debug(const std::string_view msg);
|
||||
static void fatal(const std::string_view msg);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
virtual ~Server() = default;
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "session_base.hpp"
|
||||
#include "logger.hpp"
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
SessionBase::SessionBase(TcpSocket socket)
|
||||
: socket_(std::move(socket))
|
||||
{
|
||||
}
|
||||
|
||||
void SessionBase::start()
|
||||
{
|
||||
doRead();
|
||||
}
|
||||
|
||||
void SessionBase::doRead()
|
||||
{
|
||||
using namespace boost::asio;
|
||||
|
||||
auto self = shared_from_this();
|
||||
|
||||
auto handler = [this, self](ErrorCode ec, std::size_t bytesTransferred) {
|
||||
onRead(ec, bytesTransferred);
|
||||
};
|
||||
|
||||
socket_.async_read_some(buffer(buffer_), handler);
|
||||
}
|
||||
|
||||
void SessionBase::onRead(ErrorCode ec, std::size_t bytesTransferred)
|
||||
{
|
||||
if (ec) {
|
||||
Logger::error("Read error: " + ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
std::string rawRequest(buffer_.data(), bytesTransferred);
|
||||
Logger::info("Received request:\n" + rawRequest);
|
||||
|
||||
try {
|
||||
handleRequest(rawRequest);
|
||||
} catch (const std::exception &ex) {
|
||||
Logger::error(std::string("Request handling failed: ") + ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void SessionBase::doWrite(const std::string &response)
|
||||
{
|
||||
using namespace boost::asio;
|
||||
|
||||
auto self = shared_from_this();
|
||||
auto onWriteHandler =
|
||||
[this, self](ErrorCode ec, std::size_t /*bytesTransferred*/) {
|
||||
if (ec) {
|
||||
Logger::error("Write error: " + ec.message());
|
||||
}
|
||||
|
||||
ErrorCode shutdownEc;
|
||||
if (socket_.shutdown(TcpSocket::shutdown_both, shutdownEc)) {
|
||||
Logger::warn("Shutdown error: " + shutdownEc.message());
|
||||
}
|
||||
|
||||
ErrorCode closeEc;
|
||||
if (socket_.close(closeEc)) {
|
||||
Logger::warn("Close error: " + closeEc.message());
|
||||
} else {
|
||||
Logger::info("Connection closed");
|
||||
}
|
||||
};
|
||||
|
||||
async_write(socket_, buffer(response), onWriteHandler);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "aliases.hpp"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class SessionBase : public std::enable_shared_from_this<SessionBase>
|
||||
{
|
||||
public:
|
||||
explicit SessionBase(TcpSocket socket);
|
||||
virtual ~SessionBase() = default;
|
||||
|
||||
void start();
|
||||
|
||||
protected:
|
||||
virtual void handleRequest(const std::string_view rawRequest) = 0;
|
||||
|
||||
void doRead();
|
||||
void onRead(ErrorCode ec, std::size_t bytesTransferred);
|
||||
void doWrite(const std::string &response);
|
||||
|
||||
TcpSocket socket_;
|
||||
std::array<char, 8192> buffer_;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "tcp_server_base.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
TcpServerBase::TcpServerBase(IoContext &io, unsigned short port)
|
||||
: acceptor_(io, TcpEndpoint(boost::asio::ip::tcp::v4(), port))
|
||||
{
|
||||
}
|
||||
|
||||
void TcpServerBase::start()
|
||||
{
|
||||
auto doAccept = [this]() {
|
||||
acceptor_.async_accept([this](ErrorCode ec, TcpSocket socket) {
|
||||
if (!ec) {
|
||||
Logger::info("New client connected");
|
||||
createSession(std::move(socket));
|
||||
} else {
|
||||
Logger::error("Accept error: " + ec.message());
|
||||
}
|
||||
start(); // continue accepting
|
||||
});
|
||||
};
|
||||
doAccept();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "aliases.hpp"
|
||||
|
||||
class TcpServerBase
|
||||
{
|
||||
public:
|
||||
TcpServerBase(IoContext &io, unsigned short port);
|
||||
virtual ~TcpServerBase() = default;
|
||||
|
||||
void start();
|
||||
|
||||
protected:
|
||||
virtual void createSession(TcpSocket socket) = 0;
|
||||
|
||||
TcpAcceptor acceptor_;
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "udp_server_base.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
UdpServerBase::UdpServerBase(IoContext &io, unsigned short port)
|
||||
: socket_(io, UdpEndpoint(boost::asio::ip::udp::v4(), port))
|
||||
{
|
||||
}
|
||||
|
||||
void UdpServerBase::start()
|
||||
{
|
||||
doReceive();
|
||||
}
|
||||
|
||||
void UdpServerBase::doReceive()
|
||||
{
|
||||
using namespace boost::asio;
|
||||
|
||||
auto onReceive = [this](ErrorCode ec, std::size_t bytesReceived) {
|
||||
if (!ec && bytesReceived > 0) {
|
||||
std::string_view data(buffer_.data(), bytesReceived);
|
||||
handleRequest(data, remoteEndpoint_);
|
||||
} else if (ec) {
|
||||
Logger::error("UDP receive error: " + ec.message());
|
||||
}
|
||||
doReceive(); // continue listening
|
||||
};
|
||||
|
||||
socket_.async_receive_from(buffer(buffer_), remoteEndpoint_, onReceive);
|
||||
}
|
||||
|
||||
void UdpServerBase::doSend(
|
||||
const std::string &response, const UdpEndpoint &target
|
||||
)
|
||||
{
|
||||
using namespace boost::asio;
|
||||
|
||||
auto bufferToSend = std::make_shared<std::string>(response);
|
||||
|
||||
auto onSend = [bufferToSend](ErrorCode ec, std::size_t /*bytesSent*/) {
|
||||
if (ec) {
|
||||
Logger::error("UDP send error: " + ec.message());
|
||||
}
|
||||
};
|
||||
|
||||
socket_.async_send_to(buffer(*bufferToSend), target, onSend);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "aliases.hpp"
|
||||
|
||||
class UdpServerBase
|
||||
{
|
||||
public:
|
||||
UdpServerBase(IoContext &io, unsigned short port);
|
||||
virtual ~UdpServerBase() = default;
|
||||
|
||||
void start();
|
||||
|
||||
protected:
|
||||
virtual void
|
||||
handleRequest(const std::string_view data, const UdpEndpoint &sender) = 0;
|
||||
|
||||
void doReceive();
|
||||
void doSend(const std::string &response, const UdpEndpoint &target);
|
||||
|
||||
UdpSocket socket_;
|
||||
std::array<char, 4096> buffer_;
|
||||
UdpEndpoint remoteEndpoint_;
|
||||
};
|
||||
Reference in New Issue
Block a user