From 5d56d529bcdec0c6647bbf57294b907fc95b05cc Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sun, 10 Mar 2024 14:40:54 +0100 Subject: [PATCH] make server network async, implement connecting --- src/ClientNetwork.cpp | 22 +++---- src/ServerNetwork.cpp | 144 ++++++++++++++++++++++++++---------------- src/ServerNetwork.h | 10 ++- 3 files changed, 108 insertions(+), 68 deletions(-) diff --git a/src/ClientNetwork.cpp b/src/ClientNetwork.cpp index 836bcead..cf95ad25 100644 --- a/src/ClientNetwork.cpp +++ b/src/ClientNetwork.cpp @@ -73,18 +73,6 @@ void ClientNetwork::handle_connection() { }) }); start_read(); - - // packet read and respond loop - /* - try { - auto packet = client_tcp_read(); - handle_packet(packet); - } catch (const std::exception& e) { - spdlog::error("Unhandled exception in connection handler, connection closing."); - spdlog::debug("Exception: {}", e.what()); - m_game_socket.close(); - break; - }*/ } void ClientNetwork::handle_packet(bmp::ClientPacket& packet) { @@ -300,6 +288,16 @@ void ClientNetwork::handle_browsing(bmp::ClientPacket& packet) { std::string host = details.at("host"); uint16_t port = details.at("port"); spdlog::info("Game requesting to connect to server [{}]:{}", host, port); + auto connect_result = launcher.start_server_network(host, port); + if (connect_result.has_error()) { + spdlog::error("Failed to connect to server: {}", connect_result.error()); + client_tcp_write(bmp::ClientPacket { + .purpose = bmp::ClientPurpose::ConnectError, + .raw_data = json_to_vec({ "message", connect_result.error() }), + }); + } else { + spdlog::info("Connected to server!"); + } } catch (const std::exception& e) { spdlog::error("Failed to read json for purpose 0x{:x}: {}", uint16_t(packet.purpose), e.what()); disconnect(fmt::format("Invalid json in purpose 0x{:x}, see launcher logs for more info", uint16_t(packet.purpose))); diff --git a/src/ServerNetwork.cpp b/src/ServerNetwork.cpp index 13ed2bec..f92e62fd 100644 --- a/src/ServerNetwork.cpp +++ b/src/ServerNetwork.cpp @@ -1,5 +1,6 @@ #include "ServerNetwork.h" #include "ClientInfo.h" +#include "ClientNetwork.h" #include "ClientPacket.h" #include "ClientTransport.h" #include "Identity.h" @@ -9,7 +10,6 @@ #include "ProtocolVersion.h" #include "ServerInfo.h" #include "Transport.h" -#include "ClientNetwork.h" #include "Util.h" #include #include @@ -23,7 +23,6 @@ ServerNetwork::ServerNetwork(Launcher& launcher, const ip::tcp::endpoint& ep) if (ec) { throw std::runtime_error(ec.message()); } - launcher.client_network->handle_server_packet(bmp::Packet {}); } ServerNetwork::~ServerNetwork() { @@ -47,21 +46,19 @@ void ServerNetwork::run() { }, }; version.serialize_to(version_packet.raw_data); - tcp_write(version_packet); - // main tcp recv loop - while (true) { - auto packet = tcp_read(); - handle_packet(packet); - } + tcp_write(std::move(version_packet)); + + start_read(); + + m_io.run(); } void ServerNetwork::handle_packet(const bmp::Packet& packet) { // handle ping immediately if (m_state > bmp::State::Identification && packet.purpose == bmp::Purpose::Ping) { - bmp::Packet pong { + tcp_write(bmp::Packet { .purpose = bmp::Purpose::Ping, - }; - tcp_write(pong); + }); return; } switch (m_state) { @@ -99,11 +96,10 @@ void ServerNetwork::handle_mod_download(const bmp::Packet& packet) { } // TODO: implement mod download // for now we just pretend we're all good! - bmp::Packet ok { + tcp_write(bmp::Packet { .purpose = bmp::Purpose::ModsSyncDone, - }; + }); spdlog::info("Done syncing mods"); - tcp_write(ok); break; } case bmp::Purpose::MapInfo: { @@ -178,23 +174,23 @@ void ServerNetwork::handle_identification(const bmp::Packet& packet) { }; struct bmp::ClientInfo ci { .program_version = { .major = PRJ_VERSION_MAJOR, .minor = PRJ_VERSION_MINOR, .patch = PRJ_VERSION_PATCH }, - .game_version = { - .major = launcher.game_version->major, - .minor = launcher.game_version->minor, - .patch = launcher.game_version->patch, - }, - .mod_version = { - .major = launcher.mod_version->major, - .minor = launcher.mod_version->minor, - .patch = launcher.mod_version->patch, - }, + .game_version = { + .major = launcher.game_version->major, + .minor = launcher.game_version->minor, + .patch = launcher.game_version->patch, + }, + .mod_version = { + .major = launcher.mod_version->major, + .minor = launcher.mod_version->minor, + .patch = launcher.mod_version->patch, + }, .implementation = bmp::ImplementationInfo { .value = "Official BeamMP Launcher", }, }; auto sz = ci.serialize_to(ci_packet.raw_data); ci_packet.raw_data.resize(sz); - tcp_write(ci_packet); + tcp_write(std::move(ci_packet)); break; } case bmp::Purpose::ProtocolVersionBad: @@ -215,11 +211,9 @@ void ServerNetwork::handle_identification(const bmp::Packet& packet) { spdlog::debug("Starting authentication"); m_state = bmp::State::Authentication; auto ident = launcher.identity.synchronize(); - bmp::Packet pubkey_packet { + tcp_write(bmp::Packet { .purpose = bmp::Purpose::PlayerPublicKey, - .raw_data = std::vector(ident->PublicKey.begin(), ident->PublicKey.end()) - }; - tcp_write(pubkey_packet); + .raw_data = std::vector(ident->PublicKey.begin(), ident->PublicKey.end()) }); break; } default: @@ -229,29 +223,58 @@ void ServerNetwork::handle_identification(const bmp::Packet& packet) { } } -bmp::Packet ServerNetwork::tcp_read() { - bmp::Packet packet {}; - std::vector header_buffer(bmp::Header::SERIALIZED_SIZE); - read(m_tcp_socket, buffer(header_buffer)); - bmp::Header hdr {}; - hdr.deserialize_from(header_buffer); - // vector eaten up by now, recv again - packet.raw_data.resize(hdr.size); - read(m_tcp_socket, buffer(packet.raw_data)); - packet.purpose = hdr.purpose; - packet.flags = hdr.flags; - return packet; +void ServerNetwork::tcp_read(std::function handler) { + m_tmp_header_buffer.resize(bmp::Header::SERIALIZED_SIZE); + boost::asio::async_read(m_tcp_socket, buffer(m_tmp_header_buffer), + [this, handler](auto ec, auto) { + if (ec) { + spdlog::error("Failed to read from server: {}", ec.message()); + } else { + bmp::Header hdr {}; + hdr.deserialize_from(m_tmp_header_buffer); + // vector eaten up by now, recv again + m_tmp_packet.raw_data.resize(hdr.size); + m_tmp_packet.purpose = hdr.purpose; + m_tmp_packet.flags = hdr.flags; + boost::asio::async_read(m_tcp_socket, buffer(m_tmp_packet.raw_data), + [handler, this](auto ec, auto) { + if (ec) { + spdlog::error("Failed to read from server: {}", ec.message()); + } else { + // ok! + handler(std::move(m_tmp_packet)); + } + }); + } + }); } -void ServerNetwork::tcp_write(bmp::Packet& packet) { +void ServerNetwork::tcp_write(bmp::Packet&& packet, std::function handler) { // finalize the packet (compress etc) and produce header auto header = packet.finalize(); + + auto owned_packet = std::make_shared(std::move(packet)); // serialize header - std::vector header_data(bmp::Header::SERIALIZED_SIZE); - header.serialize_to(header_data); - // write header and packet data - write(m_tcp_socket, buffer(header_data)); - write(m_tcp_socket, buffer(packet.raw_data)); + auto header_data = std::make_shared>(bmp::Header::SERIALIZED_SIZE); + header.serialize_to(*header_data); + std::array buffers = { + buffer(*header_data), + buffer(owned_packet->raw_data) + }; + boost::asio::async_write(m_tcp_socket, buffers, + [this, header_data, owned_packet, handler](auto ec, auto size) { + spdlog::debug("Wrote {} bytes for 0x{:x} to server", size, int(owned_packet->purpose)); + if (handler) { + handler(ec); + } else { + if (ec) { + spdlog::error("Failed to send packet of type 0x{:x} to server", int(owned_packet->purpose)); + } else { + // ok! + spdlog::debug("Sent packet of type 0x{:x} to server", int(owned_packet->purpose)); + } + } + }); } bmp::Packet ServerNetwork::udp_read(ip::udp::endpoint& out_ep) { @@ -268,20 +291,25 @@ bmp::Packet ServerNetwork::udp_read(ip::udp::endpoint& out_ep) { void ServerNetwork::udp_write(bmp::Packet& packet) { auto header = packet.finalize(); - std::vector data(header.size + bmp::Header::SERIALIZED_SIZE); - auto offset = header.serialize_to(data); - std::copy(packet.raw_data.begin(), packet.raw_data.end(), data.begin() + static_cast(offset)); - m_udp_socket.send_to(buffer(data), m_udp_ep, {}); + auto data = std::make_shared>(header.size + bmp::Header::SERIALIZED_SIZE); + auto offset = header.serialize_to(*data); + std::copy(packet.raw_data.begin(), packet.raw_data.end(), data->begin() + static_cast(offset)); + m_udp_socket.async_send_to(buffer(*data), m_udp_ep, [data](auto ec, auto size) { + if (ec) { + spdlog::error("Failed to UDP write to server: {}", ec.message()); + } else { + spdlog::info("Wrote {} bytes to server via UDP", size); + } + }); } void ServerNetwork::handle_session_setup(const bmp::Packet& packet) { switch (packet.purpose) { case bmp::Purpose::PlayersVehiclesInfo: { spdlog::debug("Players and vehicles info: {} bytes ({} bytes on arrival)", packet.get_readable_data().size(), packet.raw_data.size()); // TODO: Send to game - bmp::Packet ready { + tcp_write(bmp::Packet { .purpose = bmp::Purpose::SessionReady, - }; - tcp_write(ready); + }); break; } case bmp::Purpose::StateChangePlaying: { @@ -303,3 +331,11 @@ void ServerNetwork::handle_playing(const bmp::Packet& packet) { break; } } + +void ServerNetwork::start_read() { + tcp_read([this](auto&& packet) { + spdlog::debug("Got packet 0x{:x} from server", int(packet.purpose)); + handle_packet(packet); + start_read(); + }); +} diff --git a/src/ServerNetwork.h b/src/ServerNetwork.h index a55c7fd2..e5b65ebf 100644 --- a/src/ServerNetwork.h +++ b/src/ServerNetwork.h @@ -19,10 +19,12 @@ class ServerNetwork { void run(); private: + void start_read(); + /// Reads a single packet from the TCP stream. Blocks all other reads (not writes). - bmp::Packet tcp_read(); + void tcp_read(std::function handler); /// Writes the packet to the TCP stream. Blocks all other writes. - void tcp_write(bmp::Packet& packet); + void tcp_write(bmp::Packet&& packet, std::function handler = nullptr); /// Reads a packet from the given UDP socket, returning the client's endpoint as an out-argument. bmp::Packet udp_read(ip::udp::endpoint& out_ep); @@ -40,6 +42,10 @@ class ServerNetwork { ip::tcp::socket m_tcp_socket { m_io }; ip::udp::socket m_udp_socket { m_io }; + // these two tmp fields are used for temporary reading into by read, don't use anywhere else please + bmp::Packet m_tmp_packet {}; + std::vector m_tmp_header_buffer {}; + bmp::State m_state {}; uint64_t m_udp_magic {};