Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20640e766e | |||
| 75563091c1 | |||
| a4c84379ff | |||
| 827cc07175 |
@@ -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");
|
||||
}
|
||||
@@ -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_};
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "echo_client_cli.hpp"
|
||||
#include <boost/program_options/value_semantic.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_, "<HOST>"), "Server host (IP or DNS)")
|
||||
("port,p", withDefault(port_, "<PORT>"), "Server port")
|
||||
("message,m", withDefault(message_, "<TEXT>"), "Message to send");
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
std::string EchoClientCli::getHost() const
|
||||
{
|
||||
return vm_.at("host").as<std::string>();
|
||||
}
|
||||
int EchoClientCli::getPort() const
|
||||
{
|
||||
return vm_.at("port").as<int>();
|
||||
}
|
||||
std::string EchoClientCli::getMessage() const
|
||||
{
|
||||
return vm_.at("message").as<std::string>();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_base.hpp"
|
||||
#include "option_utils.hpp"
|
||||
#include <string>
|
||||
|
||||
class EchoClientCli : public CliBase, protected OptionUtils
|
||||
{
|
||||
public:
|
||||
EchoClientCli();
|
||||
|
||||
std::string getHost() const;
|
||||
int getPort() const;
|
||||
std::string getMessage() const;
|
||||
|
||||
protected:
|
||||
void setupOptions() override;
|
||||
|
||||
private:
|
||||
// defaults (used only to show defaults in help via withDefault)
|
||||
std::string host_;
|
||||
int port_;
|
||||
std::string message_;
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "logger.hpp"
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_io.hpp>
|
||||
|
||||
void Logger::init(bool toConsole, const std::string_view filePath)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
class OptionUtils
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
using OptionValue = boost::program_options::typed_value<T> *;
|
||||
|
||||
template <typename T>
|
||||
static OptionValue<T>
|
||||
withDefault(const T &value, const char *name = nullptr);
|
||||
|
||||
protected:
|
||||
OptionUtils() = default;
|
||||
~OptionUtils() = default;
|
||||
};
|
||||
|
||||
#include "option_utils.tpp"
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "option_utils.hpp"
|
||||
|
||||
template <typename T>
|
||||
OptionUtils::OptionValue<T>
|
||||
OptionUtils::withDefault(const T &value, const char *name)
|
||||
{
|
||||
auto v = boost::program_options::value<T>()->default_value(value);
|
||||
if (name) {
|
||||
v->value_name(name); // Optional
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
virtual ~Server() = default;
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#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_;
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
#include <boost/asio.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Hello, Boost + C++23 + xmake!\n";
|
||||
|
||||
boost::asio::io_context io;
|
||||
boost::asio::steady_timer timer(io, std::chrono::seconds(1));
|
||||
timer.async_wait([](const boost::system::error_code &) {
|
||||
std::cout << "Timer fired after 1 second!\n";
|
||||
});
|
||||
|
||||
io.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
#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>"), "Port to listen on")
|
||||
("address,a", withDefault(address_, "<ADDRESS>"), "Bind address (e.g. 0.0.0.0)");
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
int EchoServerCli::getPort() const
|
||||
{
|
||||
return vm_.at("port").as<int>();
|
||||
}
|
||||
std::string EchoServerCli::getAddress() const
|
||||
{
|
||||
return vm_.at("address").as<std::string>();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "cli_base.hpp"
|
||||
#include "option_utils.hpp"
|
||||
#include <string>
|
||||
|
||||
class EchoServerCli : public CliBase, protected OptionUtils
|
||||
{
|
||||
public:
|
||||
EchoServerCli();
|
||||
|
||||
int getPort() const;
|
||||
std::string getAddress() const;
|
||||
|
||||
protected:
|
||||
void setupOptions() override;
|
||||
|
||||
private:
|
||||
int port_;
|
||||
std::string address_;
|
||||
};
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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"
|
||||
-- Echo server target
|
||||
target "echo_server"
|
||||
do
|
||||
set_kind "binary"
|
||||
add_files "src/*.cpp"
|
||||
add_links "boost_system"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user