Реализован сервер asio

This commit is contained in:
Alexander Zhirov 2022-09-20 16:37:06 +03:00
parent aa93763bb4
commit 28e33863e4
8 changed files with 416 additions and 115 deletions

136
lesson_04/ap/ap.cpp Normal file
View File

@ -0,0 +1,136 @@
/*
* ap.cpp
*
* Created on: 05 сен. 2022 г.
* Author: alexander
*/
#include <ap/ap.hpp>
namespace ap
{
ConfigOption::ConfigOption(const std::string &longParameter, const char shortParameter, const hasArg ha) :
_longParameter(nullptr), _shortParameter(shortParameter), _ha(ha)
{
_longParameter = new char[longParameter.length() + 1];
strcpy(_longParameter, longParameter.c_str());
}
const char* ConfigOption::getLongParameter() const
{
return _longParameter;
}
const char& ConfigOption::getShortParameter() const
{
return _shortParameter;
}
const hasArg& ConfigOption::getPresenceArgument() const
{
return _ha;
}
Option::Option() : _set(false)
{
}
void Option::set()
{
_set = true;
}
void Option::push(const std::string &value)
{
_values.push_back(value);
}
std::vector<std::string>& Option::getValues()
{
return _values;
}
bool Option::isSet() const
{
return _set;
}
void Hub::_createArguments(const std::vector<ConfigOption> &options, bool silence)
{
_longOptions = new struct option[options.size() + 1];
_sizeOptions = options.size();
std::string temp;
if (silence)
temp.push_back(':');
for (auto const &opt : options | boost::adaptors::indexed(0))
{
_longOptions[opt.index()].name = opt.value().getLongParameter();
_longOptions[opt.index()].has_arg = opt.value().getPresenceArgument();
_longOptions[opt.index()].flag = nullptr;
_longOptions[opt.index()].val = opt.value().getShortParameter();
temp.push_back(opt.value().getShortParameter());
switch (opt.value().getPresenceArgument())
{
case hasArg::OPTIONAL:
temp.push_back(':');
/* no break */
case hasArg::REQUIRED:
temp.push_back(':');
break;
case hasArg::NO:
break;
}
_arguments[opt.value().getShortParameter()].first = false;
}
_longOptions[options.size()].name = nullptr;
_longOptions[options.size()].has_arg = 0;
_longOptions[options.size()].flag = nullptr;
_longOptions[options.size()].val = 0;
_shortOptions = new char[temp.size() + 1];
strcpy(_shortOptions, temp.c_str());
}
Hub::Hub(const std::vector<ConfigOption> &options, bool silence) :
_longOptions(nullptr), _shortOptions(nullptr)
{
_createArguments(options, silence);
}
void Hub::readArguments(int argc, char *argv[], void (*_callback)())
{
int next_option;
while ((next_option = getopt_long(argc, argv, _shortOptions, _longOptions, nullptr)) != -1)
{
if (_arguments.count(next_option))
{
_arguments[next_option].first = true;
_arguments[next_option].second.set();
if (optarg)
_arguments[next_option].second.push(std::string(optarg));
}
if (next_option == '?' && _callback)
_callback();
}
}
Option Hub::getOption(char key) const
{
return _arguments.count(key) && _arguments.at(key).first ? _arguments.at(key).second : Option();
}
Hub::~Hub()
{
delete[] _shortOptions;
for (size_t i = 0; i < _sizeOptions; ++i)
{
delete[] _longOptions[i].name;
}
delete[] _longOptions;
}
}

68
lesson_04/ap/ap.hpp Normal file
View File

@ -0,0 +1,68 @@
/*
* ap.hpp
*
* Created on: 05 сен. 2022 г.
* Author: alexander
*/
#pragma once
#include <getopt.h>
#include <vector>
#include <string>
#include <cstring>
#include <map>
#include <boost/range/adaptors.hpp>
namespace ap
{
enum hasArg
{
NO, REQUIRED, OPTIONAL
};
typedef std::vector<std::string> (*_handler)(const std::vector<std::string>&);
class ConfigOption
{
private:
char *_longParameter;
const char _shortParameter;
const hasArg _ha;
public:
ConfigOption(const std::string &longParameter, const char shortParameter, const hasArg ha);
const char* getLongParameter() const;
const char& getShortParameter() const;
const hasArg& getPresenceArgument() const;
};
class Option
{
private:
std::vector<std::string> _values;
bool _set;
public:
Option();
void push(const std::string &value);
std::vector<std::string>& getValues();
void set();
bool isSet() const;
};
class Hub
{
private:
struct option *_longOptions;
size_t _sizeOptions;
char *_shortOptions;
std::map<char, std::pair<bool, Option>> _arguments;
void _createArguments(const std::vector<ConfigOption> &options, bool silence);
public:
Hub(const std::vector<ConfigOption> &options, bool silence = true);
void readArguments(int argc, char *argv[], void (*_callback)() = nullptr);
Option getOption(char key) const;
~Hub();
};
}

17
lesson_04/asio.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<cdtprojectproperties>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.IncludePaths">
<language id="org.eclipse.cdt.core.g++" name="C++ Source File">
<includepath workspace_path="true">/${ProjName}</includepath>
</language>
<language id="org.eclipse.cdt.core.gcc" name="C Source File"/>
<language name="Object File"/>
<language id="org.eclipse.cdt.core.assembly" name="Assembly Source File"/>
</section>
<section name="org.eclipse.cdt.internal.ui.wizards.settingswizards.Macros">
<language id="org.eclipse.cdt.core.g++" name="C++ Source File"/>
<language id="org.eclipse.cdt.core.gcc" name="C Source File"/>
<language name="Object File"/>
<language id="org.eclipse.cdt.core.assembly" name="Assembly Source File"/>
</section>
</cdtprojectproperties>

View File

@ -0,0 +1,40 @@
/*
* connection.cpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#include <connection/connection.hpp>
#include <iostream>
namespace azh
{
Connection::Connection(boost::asio::io_context &ioContext) : _socket(ioContext)
{
}
tcp::socket& Connection::socket()
{
return _socket;
}
void Connection::start()
{
auto strongThis = shared_from_this();
boost::asio::async_write(_socket, boost::asio::buffer(_message),
[strongThis](const boost::system::error_code &error, size_t bytesTransferred)
{
if (error)
std::cerr << "Failed to send message!" << std::endl;
else
std::cout << "Sent " << bytesTransferred << " bytes" << std::endl;
});
}
Connection::pointer Connection::create(boost::asio::io_context &ioContext)
{
return pointer(new Connection(ioContext));
}
}

View File

@ -0,0 +1,32 @@
/*
* connection.hpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#pragma once
#include <boost/asio.hpp>
#include <memory>
namespace azh
{
using boost::asio::ip::tcp;
class Connection : public std::enable_shared_from_this<Connection>
{
public:
using pointer = std::shared_ptr<Connection>;
static pointer create(boost::asio::io_context& ioContext);
tcp::socket& socket();
void start();
private:
tcp::socket _socket;
std::string _message = "Hello, client!\n";
private:
explicit Connection(boost::asio::io_context& ioContext);
};
}

View File

@ -1,134 +1,54 @@
/*
* Ссылка на boost урок:
* https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio/tutorial.html
*/
#include <ctime>
#include <functional>
#include <ap/ap.hpp>
#include <server/tcp_server.hpp>
#include <iostream>
#include <memory>
#include <string>
#include <boost/system/error_code.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
const int echo_port = 1300;
std::string make_daytime_string()
void server(int port)
{
using namespace std;
// time_t, time и ctime;
time_t now = time(0);
return ctime(&now);
using namespace azh;
TCPServer server(IPV::V4, port);
server.run();
}
// Указатель shared_ptr и enable_shared_from_this нужны для того,
// чтобы сохранить объект tcp_connection до завершения выполнения операции.
class TcpConnection: public std::enable_shared_from_this<TcpConnection>
int main(int argc, char *argv[])
{
public:
typedef std::shared_ptr<TcpConnection> pointer;
ap::Hub hub( { { "port", 'p', ap::REQUIRED }, { "host", 'h', ap::OPTIONAL }, { "server", 's', ap::NO }, { "client", 'c', ap::NO } });
hub.readArguments(argc, argv);
static pointer create(boost::asio::io_context &io_context)
auto optionPort = hub.getOption('p');
if (!optionPort.isSet())
{
return pointer(new TcpConnection(io_context));
std::cerr << "Порт не был установлен!" << std::endl;
exit(EXIT_FAILURE);
}
tcp::socket& socket()
int port = std::stoi(optionPort.getValues()[0]);
auto optionClient = hub.getOption('c');
auto optionServer = hub.getOption('s');
if (optionClient.isSet())
{
return socket_;
}
// В методе start(), вызывается asio::async_write(), отправляющий данные клиенту.
// Здесь используется asio::async_write(), вместо ip::tcp::socket::async_write_some(), чтобы весь блок данных был гарантированно отправлен.
void start()
{
// The data to be sent is stored in the class member message_ as we need to keep the data valid until the asynchronous operation is complete.
message_ = make_daytime_string();
auto s = shared_from_this();
// Здесь вместо boost::bind используется std::bind, чтобы уменьшить число зависимостей от Boost.
// Он не работает с плейсхолдерами из Boost.
// В комментариях указаны альтернативные плейсхолдеры.
boost::asio::async_write(socket_, boost::asio::buffer(message_),
// handle_write() выполнит обработку запроса клиента.
[s](const boost::system::error_code &error, size_t bytes_transferred)
{
s->handle_write(error, bytes_transferred);
}
);
}
private:
TcpConnection(boost::asio::io_context &io_context) : socket_(io_context)
{
}
void handle_write(const boost::system::error_code& /*error*/, size_t bytes_transferred)
{
std::cout << "Bytes transferred: " << bytes_transferred << std::endl;
}
private:
tcp::socket socket_;
std::string message_;
};
class TcpServer
{
public:
// В конструкторе инициализируется акцептор, начинается прослушивание TCP порта.
TcpServer(boost::asio::io_context &io_context) : io_context_(io_context), acceptor_(io_context, tcp::endpoint(tcp::v4(), echo_port))
{
start_accept();
}
private:
// Метод start_accept() создаёт сокет и выполняет асинхронный `accept()`, при соединении.
void start_accept()
{
TcpConnection::pointer new_connection = TcpConnection::create(io_context_);
acceptor_.async_accept(new_connection->socket(), [this, new_connection](const boost::system::error_code &error)
auto optionHost = hub.getOption('h');
if (!optionHost.isSet())
{
this->handle_accept(new_connection, error);
}
);
}
// Метод handle_accept() вызывается, когда асинхронный accept, инициированный в start_accept() завершается.
// Она выполняет обработку запроса клиента и запуск нового акцептора.
void handle_accept(TcpConnection::pointer new_connection, const boost::system::error_code &error)
{
if (!error)
{
new_connection->start();
std::cerr << "Адрес не был установлен!" << std::endl;
exit(EXIT_FAILURE);
}
start_accept();
auto host = optionHost.getValues()[0];
// client(port, host);
}
private:
boost::asio::io_context &io_context_;
tcp::acceptor acceptor_;
};
int main()
{
try
else if (optionServer.isSet())
server(port);
else
{
// io_context предоставляет службы ввода-вывода, которые будет использовать сервер, такие как сокеты.
boost::asio::io_context io_context;
TcpServer server(io_context);
// Запуск асинхронных операций.
io_context.run();
}
catch (const std::exception &e)
{
std::cerr << e.what() << std::endl;
std::cerr << "Необходимо установить режим запуска:" << std::endl << "\t-c\t--client\tЗапуск в режиме клиента" << std::endl
<< "\t-s\t--server\tЗапуск в режиме сервера" << std::endl << "\t-h\t--host\t\tУказать IP-адрес сервера" << std::endl
<< "\t-p\t--port\t\tУказать порт" << std::endl;
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
return 0;
}

View File

@ -0,0 +1,54 @@
/*
* server.cpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#include <server/tcp_server.hpp>
#include <connection/connection.hpp>
#include <iostream>
namespace azh
{
using boost::asio::ip::tcp;
TCPServer::TCPServer(IPV ipv, int port) : _ipVersion(ipv), _port(port), _acceptor(_ioContext,
tcp::endpoint(_ipVersion == IPV::V4 ? tcp::v4() : tcp::v6(), _port))
{
}
TCPServer::~TCPServer()
{
}
int TCPServer::run()
{
try
{
startAccept();
_ioContext.run();
}
catch (std::exception &e)
{
std::cerr << e.what() << std::endl;
return -1;
}
return 0;
}
void TCPServer::startAccept()
{
auto connection = Connection::create(_ioContext);
_acceptor.async_accept(connection->socket(), [connection, this](const boost::system::error_code &error)
{
if (!error)
{
connection->start();
}
startAccept();
});
}
}

View File

@ -0,0 +1,34 @@
/*
* server.hpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#pragma once
#include <boost/asio.hpp>
namespace azh
{
enum class IPV
{
V4, V6
};
class TCPServer
{
public:
TCPServer(IPV ipv, int port);
virtual ~TCPServer();
int run();
private:
IPV _ipVersion;
int _port;
boost::asio::io_context _ioContext;
boost::asio::ip::tcp::acceptor _acceptor;
private:
void startAccept();
};
}