#pragma once #include #include #include #include #include #include #include "chat_messages.hpp" #include "check_policy.hpp" namespace chat { using namespace chat; using namespace boost; using boost::asio::ip::tcp; /* receive_policy and send_policy must provide the static methods * that decide what happens with the incoming / outgoing messages. * you can read about the exact requirements in check_policy.hpp */ template class client_network_manager { /* compile-time checks for whether policies are valid or not */ static_assert(is_valid_receive_policy::value, "Receive policy does not supply neccessary methods!"); static_assert(is_valid_send_policy::value, "Send policy does not supply neccessary methods!"); /* members */ private: asio::io_service& _ios; tcp::socket _socket; asio::streambuf _isb;//<-------. input stream asio::streambuf _osb;//<---. | output stream std::istream _is;//--------|---' std::ostream _os;//--------' std::string _login; std::vector> _subscriptions; /* interface */ public: client_network_manager(asio::io_service& ioservice, tcp::resolver::iterator epit, std::string login) : _ios(ioservice), _socket(_ios), _isb(), _osb(), _is(&_isb), _os(&_osb), _login(login) { connect(epit); } void send(chat_message message) { if (!send_policy::check_msg_length(message)) return; _ios.post([this, message] { asio::async_write(_socket, asio::buffer(message.get()), [](boost::system::error_code, size_t) {}); }); } void close_connection() { _ios.post([this] { _os << chat_message(message::BYE); asio::write(_socket, _osb); // TODO: decide what to do with last server message _socket.close(); _ios.stop(); }); } void subscribe(std::function callback) { _subscriptions.push_back(std::move(callback)); } /* internal methods */ private: /* throws if something went wrong on the network */ static void throw_if_error(const boost::system::error_code& ec) { if (ec) throw std::runtime_error("Networking error: " + ec.message()); } /* asynchronously connect to the host epit points to */ void connect(tcp::resolver::iterator epit) { asio::async_connect ( _socket, epit, std::bind(&client_network_manager::handshake, this, std::placeholders::_1, std::placeholders::_2) ); } /* handles the handshake as defined in the common protocol */ void handshake(boost::system::error_code ec, tcp::resolver::iterator) { throw_if_error(ec); _os << chat_message(message::HELLO); _os << chat_message(message::NEPTUN, _login); std::reverse(_login.begin(), _login.end()); // passw is neptun reversed _os << chat_message(message::PASSW, _login); asio::write(_socket, _osb); // handshake is handled synchronously std::string data; for (int i = 0; i < 4; ++i) // hello + 3 responses for messages above data += receive_message_sync() += '\n'; receive_policy::handshake_do_what(chat_message(message::SERVER_DIRECTION, data)); receive(); // then flow goes to receive-loop } /* helper method used by handshake() */ std::string receive_message_sync() { boost::system::error_code ec; std::string data; asio::read_until(_socket, _isb, byte(message::TERM), ec); std::getline(_is, data, byte(message::TERM)); throw_if_error(ec); return data.substr(1); } /* asynchronously receive a message, and decide what to do with it */ void receive() { asio::async_read_until (_socket, _isb, byte(message::TERM), [this](boost::system::error_code ec, size_t) { throw_if_error(ec); std::string data; std::getline(_is, data, byte(message::TERM)); char header = data[0]; // get command byte auto content = data.substr(1, data.size()-1); // minus command & term bytes chat_message msg(static_cast(header), content); switch (header) // decide what to do { case byte(message::PING): send(chat_message(message::PONG)); break; case byte(message::SERVER_DIRECTION): receive_policy::serverdirection_do_what (chat_message(message::SERVER_DIRECTION, content)); break; case byte(message::MESSAGE): receive_policy::message_do_what (chat_message(message::MESSAGE, content)); break; case byte(message::LOGIN): receive_policy::login_do_what (chat_message(message::LOGIN, content)); break; case byte(message::LOGOUT): receive_policy::logout_do_what (chat_message(message::LOGOUT, content)); break; default: receive_policy::invalid_msg_do_what(msg); } for (auto& fun : _subscriptions) // notify subscribers fun(msg); receive(); // receive the next message }); } }; }