From 28e33863e42210c93a6d11f11077ff18ec4587cd Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Tue, 20 Sep 2022 16:37:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=20asi?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lesson_04/ap/ap.cpp | 136 +++++++++++++++++++++++++ lesson_04/ap/ap.hpp | 68 +++++++++++++ lesson_04/asio.xml | 17 ++++ lesson_04/connection/connection.cpp | 40 ++++++++ lesson_04/connection/connection.hpp | 32 ++++++ lesson_04/main.cpp | 150 +++++++--------------------- lesson_04/server/tcp_server.cpp | 54 ++++++++++ lesson_04/server/tcp_server.hpp | 34 +++++++ 8 files changed, 416 insertions(+), 115 deletions(-) create mode 100644 lesson_04/ap/ap.cpp create mode 100644 lesson_04/ap/ap.hpp create mode 100644 lesson_04/asio.xml create mode 100644 lesson_04/connection/connection.cpp create mode 100644 lesson_04/connection/connection.hpp create mode 100644 lesson_04/server/tcp_server.cpp create mode 100644 lesson_04/server/tcp_server.hpp diff --git a/lesson_04/ap/ap.cpp b/lesson_04/ap/ap.cpp new file mode 100644 index 0000000..1fa29d0 --- /dev/null +++ b/lesson_04/ap/ap.cpp @@ -0,0 +1,136 @@ +/* + * ap.cpp + * + * Created on: 05 сен. 2022 г. + * Author: alexander + */ + +#include + +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& Option::getValues() +{ + return _values; +} + +bool Option::isSet() const +{ + return _set; +} + +void Hub::_createArguments(const std::vector &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 &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; +} + +} diff --git a/lesson_04/ap/ap.hpp b/lesson_04/ap/ap.hpp new file mode 100644 index 0000000..8ae0de8 --- /dev/null +++ b/lesson_04/ap/ap.hpp @@ -0,0 +1,68 @@ +/* + * ap.hpp + * + * Created on: 05 сен. 2022 г. + * Author: alexander + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ap +{ + +enum hasArg +{ + NO, REQUIRED, OPTIONAL +}; + +typedef std::vector (*_handler)(const std::vector&); + +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 _values; + bool _set; +public: + Option(); + void push(const std::string &value); + std::vector& getValues(); + void set(); + bool isSet() const; +}; + +class Hub +{ +private: + struct option *_longOptions; + size_t _sizeOptions; + char *_shortOptions; + std::map> _arguments; + void _createArguments(const std::vector &options, bool silence); +public: + Hub(const std::vector &options, bool silence = true); + void readArguments(int argc, char *argv[], void (*_callback)() = nullptr); + Option getOption(char key) const; + ~Hub(); +}; + +} diff --git a/lesson_04/asio.xml b/lesson_04/asio.xml new file mode 100644 index 0000000..064843d --- /dev/null +++ b/lesson_04/asio.xml @@ -0,0 +1,17 @@ + + +
+ + /${ProjName} + + + + +
+
+ + + + +
+
diff --git a/lesson_04/connection/connection.cpp b/lesson_04/connection/connection.cpp new file mode 100644 index 0000000..defabb3 --- /dev/null +++ b/lesson_04/connection/connection.cpp @@ -0,0 +1,40 @@ +/* + * connection.cpp + * + * Created on: 20 сент. 2022 г. + * Author: alexander + */ + +#include +#include + +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)); + } +} diff --git a/lesson_04/connection/connection.hpp b/lesson_04/connection/connection.hpp new file mode 100644 index 0000000..7dd7e55 --- /dev/null +++ b/lesson_04/connection/connection.hpp @@ -0,0 +1,32 @@ +/* + * connection.hpp + * + * Created on: 20 сент. 2022 г. + * Author: alexander + */ + +#pragma once + +#include +#include + +namespace azh +{ + using boost::asio::ip::tcp; + + class Connection : public std::enable_shared_from_this + { + public: + using pointer = std::shared_ptr; + + 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); + }; + +} diff --git a/lesson_04/main.cpp b/lesson_04/main.cpp index 846194d..818e931 100644 --- a/lesson_04/main.cpp +++ b/lesson_04/main.cpp @@ -1,134 +1,54 @@ -/* - * Ссылка на boost урок: - * https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio/tutorial.html - */ - -#include -#include +#include +#include #include -#include -#include -#include -#include -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 +int main(int argc, char *argv[]) { -public: - typedef std::shared_ptr 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; } - diff --git a/lesson_04/server/tcp_server.cpp b/lesson_04/server/tcp_server.cpp new file mode 100644 index 0000000..850fc2a --- /dev/null +++ b/lesson_04/server/tcp_server.cpp @@ -0,0 +1,54 @@ +/* + * server.cpp + * + * Created on: 20 сент. 2022 г. + * Author: alexander + */ + +#include +#include +#include + +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(); + }); + } +} diff --git a/lesson_04/server/tcp_server.hpp b/lesson_04/server/tcp_server.hpp new file mode 100644 index 0000000..c5f34d8 --- /dev/null +++ b/lesson_04/server/tcp_server.hpp @@ -0,0 +1,34 @@ +/* + * server.hpp + * + * Created on: 20 сент. 2022 г. + * Author: alexander + */ + +#pragma once + +#include + +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(); + }; + +}