Compare commits

...

6 Commits

Author SHA1 Message Date
Alexander Zhirov 17736d56e5 итоговый сервер 2022-09-21 16:04:06 +03:00
Alexander Zhirov 089820116b готовый сервер 2022-09-21 09:57:37 +03:00
Alexander Zhirov 7c3a27d64a идеи для дальнейшего развтия сервера 2022-09-21 08:31:23 +03:00
Alexander Zhirov e8c1ace236 поддержка соединения 2022-09-20 17:10:09 +03:00
Alexander Zhirov 28e33863e4 Реализован сервер asio 2022-09-20 16:37:06 +03:00
Alexander Zhirov aa93763bb4 Асинхронный сервер 2022-09-08 10:31:37 +03:00
10 changed files with 613 additions and 0 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,16 @@
/*
* tcp_client.cpp
*
* Created on: 21 сент. 2022 г.
* Author: alexander
*/
#include <client/tcp_client.hpp>
namespace azh
{
TCPClient::TCPClient()
{
}
}

View File

@ -0,0 +1,19 @@
/*
* tcp_client.hpp
*
* Created on: 21 сент. 2022 г.
* Author: alexander
*/
#pragma once
namespace azh
{
class TCPClient
{
public:
TCPClient();
};
}

View File

@ -0,0 +1,103 @@
/*
* tcp_connection.cpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#include <connection/tcp_connection.hpp>
#include <iostream>
namespace azh
{
TCPConnection::TCPConnection(io::ip::tcp::socket&& socket) : _socket(std::move(socket))
{
boost::system::error_code ec;
std::stringstream name;
name << _socket.remote_endpoint();
_username = name.str();
}
tcp::socket& TCPConnection::socket()
{
return _socket;
}
void TCPConnection::start(MessageHandler&& messageHandler, ErrorHandler&& errorHandler)
{
_messageHandler = std::move(messageHandler);
_errorHandler = std::move(errorHandler);
asyncRead();
}
void TCPConnection::post(const std::string& message)
{
bool queueIdle = _outgoingMessages.empty();
_outgoingMessages.push(message);
if (queueIdle)
asyncWrite();
}
void TCPConnection::asyncRead()
{
io::async_read_until(_socket, _streamBuf, '\n', [self = shared_from_this()]
(boost::system::error_code ec, size_t bytesTransferred) {
self->onRead(ec, bytesTransferred);
});
}
void TCPConnection::onRead(boost::system::error_code &ec, size_t bytesTransferred)
{
if (ec)
{
_socket.close(ec);
_errorHandler();
return;
}
std::stringstream message;
message << _username << ": " << std::istream(&_streamBuf).rdbuf();
_streamBuf.consume(bytesTransferred);
_messageHandler(message.str());
asyncRead();
}
void TCPConnection::asyncWrite()
{
io::async_write(_socket, io::buffer(_outgoingMessages.front()), [self = shared_from_this()]
(boost::system::error_code ec, size_t bytesTransferred) {
self->onWrite(ec, bytesTransferred);
});
}
void TCPConnection::onWrite(boost::system::error_code &ec, size_t bytesTransferred)
{
if (ec)
{
_socket.close(ec);
_errorHandler();
return;
}
_outgoingMessages.pop();
if (!_outgoingMessages.empty())
asyncWrite();
}
TCPConnection::Pointer TCPConnection::create(io::ip::tcp::socket&& socket)
{
return Pointer(new TCPConnection(std::move(socket)));
}
}

View File

@ -0,0 +1,57 @@
/*
* tcp_connection.hpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#pragma once
#include <boost/asio.hpp>
#include <memory>
#include <queue>
namespace azh
{
using boost::asio::ip::tcp;
namespace io = boost::asio;
using MessageHandler = std::function<void(std::string)>;
using ErrorHandler = std::function<void()>;
class TCPConnection : public std::enable_shared_from_this<TCPConnection>
{
public:
using Pointer = std::shared_ptr<TCPConnection>;
static Pointer create(io::ip::tcp::socket&& socket);
inline const std::string& getUsername() const
{
return _username;
}
tcp::socket& socket();
void start(MessageHandler&& messageHandler, ErrorHandler&& errorHandler);
void post(const std::string& message);
private:
tcp::socket _socket;
std::string _username;
std::queue<std::string> _outgoingMessages;
io::streambuf _streamBuf {65536};
MessageHandler _messageHandler;
ErrorHandler _errorHandler;
private:
explicit TCPConnection(io::ip::tcp::socket&& socket);
void asyncRead();
void onRead(boost::system::error_code &ec, size_t bytesTransferred);
void asyncWrite();
void onWrite(boost::system::error_code &ec, size_t bytesTransferred);
};
}

71
lesson_04/main.cpp Normal file
View File

@ -0,0 +1,71 @@
#include <ap/ap.hpp>
#include <server/tcp_server.hpp>
#include <connection/tcp_connection.hpp>
#include <iostream>
void server(int port)
{
using namespace azh;
TCPServer server(IPV::V4, port);
server.onJoin = [](TCPConnection::Pointer server)
{
std::cout << "User has joined the server " << server->getUsername() << std::endl;
};
server.onLeave = [](TCPConnection::Pointer server)
{
std::cout << "User has left the server " << server->getUsername() << std::endl;
};
server.onClientMessage = [&server](const std::string& message)
{
server.broadcast(message);
};
server.run();
}
int main(int argc, char *argv[])
{
ap::Hub hub( { { "port", 'p', ap::REQUIRED }, { "host", 'h', ap::OPTIONAL }, { "server", 's', ap::NO }, { "client", 'c', ap::NO } });
hub.readArguments(argc, argv);
auto optionPort = hub.getOption('p');
if (!optionPort.isSet())
{
std::cerr << "Порт не был установлен!" << std::endl;
exit(EXIT_FAILURE);
}
int port = std::stoi(optionPort.getValues()[0]);
auto optionClient = hub.getOption('c');
auto optionServer = hub.getOption('s');
if (optionClient.isSet())
{
auto optionHost = hub.getOption('h');
if (!optionHost.isSet())
{
std::cerr << "Адрес не был установлен!" << std::endl;
exit(EXIT_FAILURE);
}
auto host = optionHost.getValues()[0];
// client(port, host);
}
else if (optionServer.isSet())
server(port);
else
{
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 0;
}

View File

@ -0,0 +1,73 @@
/*
* tcp_server.cpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#include <connection/tcp_connection.hpp>
#include <server/tcp_server.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))
{
}
int TCPServer::run()
{
try
{
startAccept();
_ioContext.run();
}
catch (std::exception &e)
{
std::cerr << e.what() << std::endl;
return -1;
}
return 0;
}
void TCPServer::broadcast(const std::string &message)
{
for (auto &connection : _connections)
{
connection->post(message);
}
}
void TCPServer::startAccept()
{
_socket.emplace(_ioContext);
_acceptor.async_accept(*_socket, [this](const boost::system::error_code &error)
{
auto connection = TCPConnection::create(std::move(*_socket));
if (onJoin)
onJoin(connection);
_connections.insert(connection);
if (!error)
connection->start(
[this](const std::string& message)
{
if (onClientMessage) onClientMessage(message);
},
[&, weak = std::weak_ptr(connection)]
{
if (auto shared = weak.lock(); shared && _connections.erase(shared))
if (onLeave) onLeave(shared);
}
);
startAccept();
});
}
}

View File

@ -0,0 +1,53 @@
/*
* tcp_server.hpp
*
* Created on: 20 сент. 2022 г.
* Author: alexander
*/
#pragma once
#include <boost/asio.hpp>
#include <connection/tcp_connection.hpp>
#include <vector>
#include <optional>
#include <unordered_set>
namespace azh
{
namespace io = boost::asio;
enum class IPV
{
V4, V6
};
class TCPServer
{
using OnJoinHandler = std::function<void(TCPConnection::Pointer)>;
using OnLeaveHandler = std::function<void(TCPConnection::Pointer)>;
using OnClientMessageHandler = std::function<void(std::string)>;
public:
TCPServer(IPV ipv, int port);
int run();
void broadcast(const std::string& message);
public:
OnJoinHandler onJoin;
OnLeaveHandler onLeave;
OnClientMessageHandler onClientMessage;
private:
IPV _ipVersion;
int _port;
io::io_context _ioContext;
io::ip::tcp::acceptor _acceptor;
std::optional<io::ip::tcp::socket> _socket;
std::unordered_set<TCPConnection::Pointer> _connections;
private:
void startAccept();
};
}