feat(echo): add echo client and server with CLI support

This commit is contained in:
Ваше Имя
2025-10-04 20:28:19 +04:00
parent a4c84379ff
commit 75563091c1
13 changed files with 314 additions and 9 deletions
+59
View File
@@ -0,0 +1,59 @@
#include "echo_client.hpp"
#include <boost/asio/connect.hpp>
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");
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "aliases.hpp"
#include "logger.hpp"
#include <string>
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_};
};
+35
View File
@@ -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<std::string>();
}
int EchoClientCli::getPort() const
{
return vm_["port"].as<int>();
}
std::string EchoClientCli::getMessage() const
{
return vm_["message"].as<std::string>();
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "cli_base.hpp"
#include "option_utils.hpp"
#include <string>
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_;
};
+26
View File
@@ -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<unsigned short>(cli.getPort());
const std::string message = cli.getMessage();
EchoClient client(io, host, port);
const bool ok = client.run(message);
return ok ? 0 : 1;
}
+27
View File
@@ -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<unsigned short>(cli.getPort());
EchoServer server(io, port);
Logger::info("EchoServer starting on port " + std::to_string(port));
server.start();
io.run();
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
#include "echo_server.hpp"
#include "echo_session.hpp"
void EchoServer::createSession(TcpSocket socket)
{
auto session = std::make_shared<EchoSession>(std::move(socket));
session->start();
}
+15
View File
@@ -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;
};
+28
View File
@@ -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<int>();
}
std::string EchoServerCli::getAddress() const
{
return vm_["address"].as<std::string>();
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "cli_base.hpp"
#include "option_utils.hpp"
#include <string>
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_;
};
+15
View File
@@ -0,0 +1,15 @@
#include "echo_session.hpp"
#include "logger.hpp"
#include <utility> // 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)
);
}
+12
View File
@@ -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;
};
+21 -9
View File
@@ -2,16 +2,28 @@ set_languages "c++23"
add_rules("plugin.compile_commands.autoupdate", { outputdir = "." }) add_rules("plugin.compile_commands.autoupdate", { outputdir = "." })
add_requires("boost", { add_requires("cmake::Boost", {
system = true, -- sudo pacman -S boost alias = "boost",
system = true,
configs = { configs = {
all = false, components = { "program_options", "log" },
system = true, -- asio
}, },
}) })
target "app" -- Echo server target
set_kind "binary" target "echo_server"
add_files "src/*.cpp" do
add_links "boost_system" 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