From 75563091c1deb935ae9d3c8a6c797c1d4571e5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=88=D0=B5=20=D0=98=D0=BC=D1=8F?= Date: Sat, 4 Oct 2025 20:28:19 +0400 Subject: [PATCH] feat(echo): add echo client and server with CLI support --- src/client/echo_client.cpp | 59 ++++++++++++++++++++++++++++++++++ src/client/echo_client.hpp | 24 ++++++++++++++ src/client/echo_client_cli.cpp | 35 ++++++++++++++++++++ src/client/echo_client_cli.hpp | 23 +++++++++++++ src/main/echo_client_main.cpp | 26 +++++++++++++++ src/main/echo_server_main.cpp | 27 ++++++++++++++++ src/server/echo_server.cpp | 8 +++++ src/server/echo_server.hpp | 15 +++++++++ src/server/echo_server_cli.cpp | 28 ++++++++++++++++ src/server/echo_server_cli.hpp | 21 ++++++++++++ src/session/echo_session.cpp | 15 +++++++++ src/session/echo_session.hpp | 12 +++++++ xmake.lua | 30 +++++++++++------ 13 files changed, 314 insertions(+), 9 deletions(-) create mode 100644 src/client/echo_client.cpp create mode 100644 src/client/echo_client.hpp create mode 100644 src/client/echo_client_cli.cpp create mode 100644 src/client/echo_client_cli.hpp create mode 100644 src/main/echo_client_main.cpp create mode 100644 src/main/echo_server_main.cpp create mode 100644 src/server/echo_server.cpp create mode 100644 src/server/echo_server.hpp create mode 100644 src/server/echo_server_cli.cpp create mode 100644 src/server/echo_server_cli.hpp create mode 100644 src/session/echo_session.cpp create mode 100644 src/session/echo_session.hpp diff --git a/src/client/echo_client.cpp b/src/client/echo_client.cpp new file mode 100644 index 0000000..9f0c425 --- /dev/null +++ b/src/client/echo_client.cpp @@ -0,0 +1,59 @@ +#include "echo_client.hpp" +#include + +EchoClient::EchoClient(IoContext &io, std::string host, unsigned short port) + : io_(io) + , host_(std::move(host)) + , port_(port) + , socket_(io_) +{ +} + +bool EchoClient::run(const std::string &message) +{ + if (!connect()) { + Logger::error("EchoClient: connect failed"); + return false; + } + const bool ok = send(message); + close(); + return ok; +} + +bool EchoClient::connect() +{ + try { + boost::asio::ip::tcp::resolver resolver(io_); + auto results = resolver.resolve(host_, std::to_string(port_)); + boost::asio::connect(socket_, results); + Logger::info( + "EchoClient: connected to " + host_ + ":" + std::to_string(port_) + ); + return true; + } catch (const std::exception &ex) { + Logger::error( + std::string("EchoClient: connect exception: ") + ex.what() + ); + return false; + } +} + +bool EchoClient::send(const std::string &message) +{ + try { + auto bytes = boost::asio::write(socket_, boost::asio::buffer(message)); + Logger::info("EchoClient: sent " + std::to_string(bytes) + " bytes"); + return true; + } catch (const std::exception &ex) { + Logger::error(std::string("EchoClient: send exception: ") + ex.what()); + return false; + } +} + +void EchoClient::close() +{ + ErrorCode ec; + socket_.shutdown(TcpSocket::shutdown_both, ec); + socket_.close(ec); + Logger::info("EchoClient: connection closed"); +} diff --git a/src/client/echo_client.hpp b/src/client/echo_client.hpp new file mode 100644 index 0000000..cf872eb --- /dev/null +++ b/src/client/echo_client.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "aliases.hpp" +#include "logger.hpp" +#include + +class EchoClient final +{ + public: + EchoClient(IoContext &io, std::string host, unsigned short port); + + bool run(const std::string &message); + + private: + bool connect(); + bool send(const std::string &message); + void close(); + + private: + IoContext &io_; + std::string host_; + unsigned short port_; + TcpSocket socket_{io_}; +}; diff --git a/src/client/echo_client_cli.cpp b/src/client/echo_client_cli.cpp new file mode 100644 index 0000000..e5b0449 --- /dev/null +++ b/src/client/echo_client_cli.cpp @@ -0,0 +1,35 @@ +#include "echo_client_cli.hpp" + +EchoClientCli::EchoClientCli() + : CliBase("Echo Client Options") + , host_("127.0.0.1") + , port_(9000) + , message_("Hello from client") +{ +} + +void EchoClientCli::setupOptions() +{ + // clang-format off + desc_.add_options() + ("help,h", "Show help") + ("host,H", withDefault(host_), "Server host (IP or DNS name)") + ("port,p", withDefault(port_), "Server port") + ("message,m", withDefault(message_), "Message to send"); + // clang-format on +} + +std::string EchoClientCli::getHost() const +{ + return vm_["host"].as(); +} + +int EchoClientCli::getPort() const +{ + return vm_["port"].as(); +} + +std::string EchoClientCli::getMessage() const +{ + return vm_["message"].as(); +} diff --git a/src/client/echo_client_cli.hpp b/src/client/echo_client_cli.hpp new file mode 100644 index 0000000..7dfc133 --- /dev/null +++ b/src/client/echo_client_cli.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "cli_base.hpp" +#include "option_utils.hpp" +#include + +class EchoClientCli : public CliBase, public OptionUtils +{ + public: + EchoClientCli(); + + std::string getHost() const; + int getPort() const; + std::string getMessage() const; + + protected: + void setupOptions() override; + + private: + std::string host_; + int port_; + std::string message_; +}; diff --git a/src/main/echo_client_main.cpp b/src/main/echo_client_main.cpp new file mode 100644 index 0000000..6dbda4f --- /dev/null +++ b/src/main/echo_client_main.cpp @@ -0,0 +1,26 @@ +#include "aliases.hpp" +#include "echo_client.hpp" +#include "echo_client_cli.hpp" +#include "logger.hpp" + +int main(int argc, char *argv[]) +{ + EchoClientCli cli; + cli.parse(argc, argv); + if (cli.isHelp()) { + cli.printHelp(); + return 0; + } + + Logger::init(true); + + IoContext io; + const std::string host = cli.getHost(); + const auto port = static_cast(cli.getPort()); + const std::string message = cli.getMessage(); + + EchoClient client(io, host, port); + const bool ok = client.run(message); + + return ok ? 0 : 1; +} diff --git a/src/main/echo_server_main.cpp b/src/main/echo_server_main.cpp new file mode 100644 index 0000000..3d6375b --- /dev/null +++ b/src/main/echo_server_main.cpp @@ -0,0 +1,27 @@ +#include "aliases.hpp" +#include "echo_server.hpp" +#include "echo_server_cli.hpp" +#include "logger.hpp" + +int main(int argc, char *argv[]) +{ + EchoServerCli cli; + cli.parse(argc, argv); + if (cli.isHelp()) { + cli.printHelp(); + return 0; + } + + Logger::init(true); + + IoContext io; + const auto port = static_cast(cli.getPort()); + EchoServer server(io, port); + + Logger::info("EchoServer starting on port " + std::to_string(port)); + server.start(); + + io.run(); + + return 0; +} diff --git a/src/server/echo_server.cpp b/src/server/echo_server.cpp new file mode 100644 index 0000000..862e329 --- /dev/null +++ b/src/server/echo_server.cpp @@ -0,0 +1,8 @@ +#include "echo_server.hpp" +#include "echo_session.hpp" + +void EchoServer::createSession(TcpSocket socket) +{ + auto session = std::make_shared(std::move(socket)); + session->start(); +} diff --git a/src/server/echo_server.hpp b/src/server/echo_server.hpp new file mode 100644 index 0000000..b91685c --- /dev/null +++ b/src/server/echo_server.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "tcp_server_base.hpp" + +class EchoServer final : public TcpServerBase +{ + public: + EchoServer(IoContext &io, unsigned short port) + : TcpServerBase(io, port) + { + } + + protected: + void createSession(TcpSocket socket) override; +}; diff --git a/src/server/echo_server_cli.cpp b/src/server/echo_server_cli.cpp new file mode 100644 index 0000000..60a4c72 --- /dev/null +++ b/src/server/echo_server_cli.cpp @@ -0,0 +1,28 @@ +#include "echo_server_cli.hpp" + +EchoServerCli::EchoServerCli() + : CliBase("Echo Server Options") + , port_(9000) + , address_("0.0.0.0") +{ +} + +void EchoServerCli::setupOptions() +{ + // clang-format off + desc_.add_options() + ("help,h", "Show help") + ("port,p", withDefault(port_), "Port to listen on") + ("address,a", withDefault(address_), "Bind address (e.g. 0.0.0.0 or 127.0.0.1)"); + // clang-format on +} + +int EchoServerCli::getPort() const +{ + return vm_["port"].as(); +} + +std::string EchoServerCli::getAddress() const +{ + return vm_["address"].as(); +} diff --git a/src/server/echo_server_cli.hpp b/src/server/echo_server_cli.hpp new file mode 100644 index 0000000..0064394 --- /dev/null +++ b/src/server/echo_server_cli.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "cli_base.hpp" +#include "option_utils.hpp" +#include + +class EchoServerCli : public CliBase, public OptionUtils +{ + public: + EchoServerCli(); + + int getPort() const; + std::string getAddress() const; + + protected: + void setupOptions() override; + + private: + int port_; + std::string address_; +}; diff --git a/src/session/echo_session.cpp b/src/session/echo_session.cpp new file mode 100644 index 0000000..c8c47b6 --- /dev/null +++ b/src/session/echo_session.cpp @@ -0,0 +1,15 @@ +#include "echo_session.hpp" +#include "logger.hpp" +#include // std::move + +EchoSession::EchoSession(TcpSocket socket) + : SessionBase(std::move(socket)) +{ +} + +void EchoSession::handleRequest(const std::string_view rawRequest) +{ + Logger::info( + std::string("EchoSession received: ") + std::string(rawRequest) + ); +} diff --git a/src/session/echo_session.hpp b/src/session/echo_session.hpp new file mode 100644 index 0000000..13a98a5 --- /dev/null +++ b/src/session/echo_session.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "session_base.hpp" + +class EchoSession final : public SessionBase +{ + public: + explicit EchoSession(TcpSocket socket); + + protected: + void handleRequest(const std::string_view rawRequest) override; +}; diff --git a/xmake.lua b/xmake.lua index fc0928b..e879bdc 100644 --- a/xmake.lua +++ b/xmake.lua @@ -2,16 +2,28 @@ set_languages "c++23" add_rules("plugin.compile_commands.autoupdate", { outputdir = "." }) -add_requires("boost", { - system = true, -- sudo pacman -S boost - +add_requires("cmake::Boost", { + alias = "boost", + system = true, configs = { - all = false, - system = true, -- asio + components = { "program_options", "log" }, }, }) -target "app" -set_kind "binary" -add_files "src/*.cpp" -add_links "boost_system" +-- Echo server target +target "echo_server" +do + set_kind "binary" + add_files("src/main/echo_server_main.cpp", "src/server/*.cpp", "src/session/*.cpp", "src/common/*.cpp") + add_includedirs("src", "src/server", "src/session", "src/common", { public = true }) + add_packages "boost" +end + +-- Echo client target +target "echo_client" +do + set_kind "binary" + add_files("src/main/echo_client_main.cpp", "src/client/*.cpp", "src/session/*.cpp", "src/common/*.cpp") + add_includedirs("src", "src/client", "src/session", "src/common", { public = true }) + add_packages "boost" +end