switch to make ClientNetwork async

This commit is contained in:
Lion Kortlepel 2024-03-09 23:22:34 +01:00
parent b9be3990c7
commit c6e07cd26b
No known key found for this signature in database
GPG Key ID: 4322FF2B4C71259B
4 changed files with 151 additions and 53 deletions

View File

@ -2,8 +2,8 @@
#include "ClientPacket.h" #include "ClientPacket.h"
#include "ClientTransport.h" #include "ClientTransport.h"
#include "Http.h" #include "Http.h"
#include "Launcher.h"
#include "Identity.h" #include "Identity.h"
#include "Launcher.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@ -28,8 +28,8 @@ void ClientNetwork::run() {
ec.message()); ec.message());
} }
ip::tcp::acceptor acceptor(m_io, listen_ep); m_acceptor = ip::tcp::acceptor(m_io, listen_ep);
acceptor.listen(ip::tcp::socket::max_listen_connections, ec); m_acceptor.listen(ip::tcp::socket::max_listen_connections, ec);
if (ec) { if (ec) {
spdlog::error("listen() failed, which is needed for the launcher to operate. " spdlog::error("listen() failed, which is needed for the launcher to operate. "
"Shutting down. spdlog::error: {}", "Shutting down. spdlog::error: {}",
@ -37,22 +37,8 @@ void ClientNetwork::run() {
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(3));
std::exit(1); std::exit(1);
} }
do { start_accept();
try { m_io.run();
spdlog::info("Waiting for the game to connect");
ip::tcp::endpoint game_ep;
auto game = acceptor.accept(game_ep, ec);
if (ec) {
spdlog::error("Failed to accept: {}", ec.message());
}
spdlog::info("Game connected!");
spdlog::debug("Game: [{}]:{}", game_ep.address().to_string(), game_ep.port());
handle_connection(std::move(game));
spdlog::warn("Game disconnected!");
} catch (const std::exception& e) {
spdlog::error("Fatal error in core network: {}", e.what());
}
} while (!*m_shutdown);
} }
ClientNetwork::ClientNetwork(Launcher& launcher, uint16_t port) ClientNetwork::ClientNetwork(Launcher& launcher, uint16_t port)
@ -65,8 +51,7 @@ ClientNetwork::~ClientNetwork() {
spdlog::debug("Client network destroyed"); spdlog::debug("Client network destroyed");
} }
void ClientNetwork::handle_connection(ip::tcp::socket&& socket) { void ClientNetwork::handle_connection() {
m_game_socket = std::move(socket);
// immediately send launcher info (first step of client identification) // immediately send launcher info (first step of client identification)
m_client_state = bmp::ClientState::ClientIdentification; m_client_state = bmp::ClientState::ClientIdentification;
@ -80,18 +65,21 @@ void ClientNetwork::handle_connection(ip::tcp::socket&& socket) {
}); });
client_tcp_write(info_packet); client_tcp_write(info_packet);
client_tcp_read([this](auto&& packet) {
handle_packet(packet);
});
// packet read and respond loop // packet read and respond loop
while (!*m_shutdown) { /*
try { try {
auto packet = client_tcp_read(); auto packet = client_tcp_read();
handle_packet(packet); handle_packet(packet);
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Unhandled exception in connection handler, connection closing."); spdlog::error("Unhandled exception in connection handler, connection closing.");
spdlog::debug("Exception: {}", e.what()); spdlog::debug("Exception: {}", e.what());
m_game_socket.close(); m_game_socket.close();
break; break;
} }*/
}
} }
void ClientNetwork::handle_packet(bmp::ClientPacket& packet) { void ClientNetwork::handle_packet(bmp::ClientPacket& packet) {
@ -213,12 +201,14 @@ void ClientNetwork::start_login() {
} }
void ClientNetwork::disconnect(const std::string& reason) { void ClientNetwork::disconnect(const std::string& reason) {
spdlog::debug("Disconnecting game: {}", reason);
bmp::ClientPacket error { bmp::ClientPacket error {
.purpose = bmp::ClientPurpose::Error, .purpose = bmp::ClientPurpose::Error,
.raw_data = json_to_vec({ "message", reason }) .raw_data = json_to_vec({ "message", reason })
}; };
client_tcp_write(error); client_tcp_write(error);
m_game_socket.close(); m_game_socket.close();
start_accept();
} }
void ClientNetwork::handle_login(bmp::ClientPacket& packet) { void ClientNetwork::handle_login(bmp::ClientPacket& packet) {
@ -366,30 +356,50 @@ void ClientNetwork::handle_server_leaving(bmp::ClientPacket& packet) {
} }
} }
bmp::ClientPacket ClientNetwork::client_tcp_read() { void ClientNetwork::client_tcp_read(std::function<void(bmp::ClientPacket&&)> handler) {
bmp::ClientPacket packet {}; m_tmp_header_buffer.resize(bmp::ClientHeader::SERIALIZED_SIZE);
std::vector<uint8_t> header_buffer(bmp::ClientHeader::SERIALIZED_SIZE); boost::asio::async_read(m_game_socket, buffer(m_tmp_header_buffer),
read(m_game_socket, buffer(header_buffer)); bind_executor(m_strand, [this, handler](auto ec, auto) {
bmp::ClientHeader hdr {}; if (ec) {
hdr.deserialize_from(header_buffer); disconnect(fmt::format("Failed to read from game: {}", ec.message()));
// vector eaten up by now, recv again } else {
packet.raw_data.resize(hdr.data_size); bmp::ClientHeader hdr {};
read(m_game_socket, buffer(packet.raw_data)); hdr.deserialize_from(m_tmp_header_buffer);
packet.purpose = hdr.purpose; // vector eaten up by now, recv again
packet.flags = hdr.flags; m_tmp_packet.raw_data.resize(hdr.data_size);
return packet; m_tmp_packet.purpose = hdr.purpose;
m_tmp_packet.flags = hdr.flags;
boost::asio::async_read(m_game_socket, buffer(m_tmp_packet.raw_data),
bind_executor(m_strand, [handler, this](auto ec, auto) {
if (ec) {
disconnect(fmt::format("Failed to read from game: {}", ec.message()));
} else {
// ok!
handler(std::move(m_tmp_packet));
}
}));
}
}));
} }
void ClientNetwork::client_tcp_write(bmp::ClientPacket& packet) { void ClientNetwork::client_tcp_write(bmp::ClientPacket& packet) {
// finalize the packet (compress etc) and produce header
auto header = packet.finalize(); auto header = packet.finalize();
// serialize header // serialize header
std::vector<uint8_t> header_data(bmp::ClientHeader::SERIALIZED_SIZE); std::vector<uint8_t> data(bmp::ClientHeader::SERIALIZED_SIZE + packet.raw_data.size());
header.serialize_to(header_data); auto offset = header.serialize_to(data);
// write header and packet data // copy packet data (yes i know ugh) to the `data` in order to send it in one go
write(m_game_socket, buffer(header_data)); std::copy(packet.raw_data.begin(), packet.raw_data.end(), data.begin() + long(offset));
write(m_game_socket, buffer(packet.raw_data)); boost::asio::async_write(m_game_socket, buffer(data),
[this](auto ec, auto) {
if (ec) {
spdlog::error("Failed to write a packet: {}", ec.message());
disconnect("Failed to send data to game");
} else {
// ok! sent all data
}
});
} }
std::vector<uint8_t> ClientNetwork::json_to_vec(const nlohmann::json& value) { std::vector<uint8_t> ClientNetwork::json_to_vec(const nlohmann::json& value) {
auto str = value.dump(); auto str = value.dump();
return std::vector<uint8_t>(str.begin(), str.end()); return std::vector<uint8_t>(str.begin(), str.end());
@ -429,3 +439,73 @@ Result<nlohmann::json, std::string> ClientNetwork::load_server_list() noexcept {
return outcome::failure(fmt::format("Failed to fetch server list from backend: {}", e.what())); return outcome::failure(fmt::format("Failed to fetch server list from backend: {}", e.what()));
} }
} }
void ClientNetwork::handle_server_packet(bmp::Packet&& packet) {
post(m_io, [packet, this] {
spdlog::info("HELLO WORLD: {}", m_listen_port);
switch (packet.purpose) {
case bmp::Invalid:
break;
case bmp::ProtocolVersion:
case bmp::ProtocolVersionOk:
case bmp::ProtocolVersionBad:
case bmp::ClientInfo:
case bmp::ServerInfo:
case bmp::PlayerPublicKey:
case bmp::AuthOk:
case bmp::AuthFailed:
case bmp::PlayerRejected:
case bmp::StartUDP:
case bmp::ModsInfo:
case bmp::MapInfo:
case bmp::ModRequest:
case bmp::ModResponse:
case bmp::ModRequestInvalid:
case bmp::ModsSyncDone:
case bmp::PlayersVehiclesInfo:
case bmp::SessionReady:
case bmp::Ping:
case bmp::VehicleSpawn:
case bmp::VehicleDelete:
case bmp::VehicleReset:
case bmp::VehicleEdited:
case bmp::VehicleCouplerChanged:
case bmp::SpectatorSwitched:
case bmp::ApplyInput:
case bmp::ApplyElectrics:
case bmp::ApplyNodes:
case bmp::ApplyBreakgroups:
case bmp::ApplyPowertrain:
case bmp::ApplyPosition:
case bmp::ChatMessage:
case bmp::Event:
case bmp::PlayerJoined:
case bmp::PlayerLeft:
case bmp::PlayerPingUpdate:
case bmp::Notification:
case bmp::Kicked:
case bmp::StateChangeIdentification:
case bmp::StateChangeAuthentication:
case bmp::StateChangeModDownload:
case bmp::StateChangeSessionSetup:
case bmp::StateChangePlaying:
case bmp::StateChangeLeaving:
break;
}
});
}
void ClientNetwork::start_accept() {
m_acceptor.async_accept(m_game_socket, [this](const auto& ec) { handle_accept(ec); });
}
void ClientNetwork::handle_accept(boost::system::error_code ec) {
if (ec) {
spdlog::error("Failed accepting game connection: {}", ec.message());
} else {
spdlog::info("Game connected!");
auto game_ep = m_game_socket.remote_endpoint();
spdlog::debug("Game: [{}]:{}", game_ep.address().to_string(), game_ep.port());
handle_connection();
}
// TODO: We should probably accept() again somewhere once the game disconnected
}

View File

@ -3,6 +3,7 @@
#include "ClientPacket.h" #include "ClientPacket.h"
#include "ClientState.h" #include "ClientState.h"
#include "Launcher.h" #include "Launcher.h"
#include "Packet.h"
#include "Sync.h" #include "Sync.h"
#include "Version.h" #include "Version.h"
@ -19,11 +20,14 @@ public:
void run(); void run();
void handle_server_packet(bmp::Packet&& packet);
private: private:
void handle_connection(ip::tcp::socket&& socket); void start_accept();
bmp::ClientPacket client_tcp_read(); void handle_accept(boost::system::error_code ec);
void handle_connection();
void client_tcp_read(std::function<void(bmp::ClientPacket&&)> handler);
void client_tcp_write(bmp::ClientPacket& packet); void client_tcp_write(bmp::ClientPacket& packet);
void handle_packet(bmp::ClientPacket& packet); void handle_packet(bmp::ClientPacket& packet);
@ -56,5 +60,12 @@ private:
Sync<bool> m_shutdown { false }; Sync<bool> m_shutdown { false };
bmp::ClientState m_client_state; bmp::ClientState m_client_state;
ip::tcp::acceptor m_acceptor { m_io };
boost::asio::strand<ip::tcp::socket::executor_type> m_strand { m_game_socket.get_executor() };
// temporary packet and header buffer for async reads
bmp::ClientPacket m_tmp_packet {};
std::vector<uint8_t> m_tmp_header_buffer {};
Launcher& launcher; Launcher& launcher;
}; };

View File

@ -1,11 +1,15 @@
#include "ServerNetwork.h" #include "ServerNetwork.h"
#include "ClientInfo.h" #include "ClientInfo.h"
#include "ClientPacket.h"
#include "ClientTransport.h"
#include "Identity.h" #include "Identity.h"
#include "ImplementationInfo.h" #include "ImplementationInfo.h"
#include "Launcher.h" #include "Launcher.h"
#include "Packet.h"
#include "ProtocolVersion.h" #include "ProtocolVersion.h"
#include "ServerInfo.h" #include "ServerInfo.h"
#include "Transport.h" #include "Transport.h"
#include "ClientNetwork.h"
#include "Util.h" #include "Util.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@ -19,6 +23,7 @@ ServerNetwork::ServerNetwork(Launcher& launcher, const ip::tcp::endpoint& ep)
if (ec) { if (ec) {
throw std::runtime_error(ec.message()); throw std::runtime_error(ec.message());
} }
launcher.client_network->handle_server_packet(bmp::Packet {});
} }
ServerNetwork::~ServerNetwork() { ServerNetwork::~ServerNetwork() {

View File

@ -89,6 +89,8 @@ int main(int argc, char** argv) {
launcher.start_game(); launcher.start_game();
} }
launcher.client_network->handle_server_packet(bmp::Packet {});
while (true) { while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }