atrooney-online-2/networkmanager.cpp
oldmud0 2925c8c891 Fail over to non-SRV master server connection
This will eliminate the connection failure dialog. Instead, the reconnect timer will run indefinitely.
2017-12-29 17:10:47 -06:00

248 lines
6.1 KiB
C++

#include "networkmanager.h"
#include "datatypes.h"
#include "debug_functions.h"
#include "lobby.h"
NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent)
{
ao_app = parent;
ms_socket = new QTcpSocket(this);
server_socket = new QTcpSocket(this);
ms_reconnect_timer = new QTimer(this);
ms_reconnect_timer->setSingleShot(true);
QObject::connect(ms_reconnect_timer, SIGNAL(timeout()), this, SLOT(retry_ms_connect()));
QObject::connect(ms_socket, SIGNAL(readyRead()), this, SLOT(handle_ms_packet()));
QObject::connect(server_socket, SIGNAL(readyRead()), this, SLOT(handle_server_packet()));
QObject::connect(server_socket, SIGNAL(disconnected()), ao_app, SLOT(server_disconnected()));
}
NetworkManager::~NetworkManager()
{
}
void NetworkManager::connect_to_master()
{
ms_socket->close();
ms_socket->abort();
#ifdef MS_FAILOVER_SUPPORTED
perform_srv_lookup();
#else
connect_to_master_nosrv();
#endif
}
void NetworkManager::connect_to_master_nosrv()
{
QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError)));
QObject::connect(ms_socket, SIGNAL(connected()),
this, SLOT(on_ms_nosrv_connect_success()));
ms_socket->connectToHost(ms_nosrv_hostname, ms_port);
}
void NetworkManager::connect_to_server(server_type p_server)
{
server_socket->close();
server_socket->abort();
server_socket->connectToHost(p_server.ip, p_server.port);
}
void NetworkManager::ship_ms_packet(QString p_packet)
{
if (!ms_socket->isOpen())
{
retry_ms_connect();
}
else
{
ms_socket->write(p_packet.toUtf8());
}
}
void NetworkManager::ship_server_packet(QString p_packet)
{
server_socket->write(p_packet.toUtf8());
}
void NetworkManager::handle_ms_packet()
{
char buffer[16384] = {0};
ms_socket->read(buffer, ms_socket->bytesAvailable());
QString in_data = buffer;
if (!in_data.endsWith("%"))
{
ms_partial_packet = true;
ms_temp_packet += in_data;
return;
}
else
{
if (ms_partial_packet)
{
in_data = ms_temp_packet + in_data;
ms_temp_packet = "";
ms_partial_packet = false;
}
}
QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts));
for (QString packet : packet_list)
{
AOPacket *f_packet = new AOPacket(packet);
ao_app->ms_packet_received(f_packet);
}
}
void NetworkManager::perform_srv_lookup()
{
#ifdef MS_FAILOVER_SUPPORTED
ms_dns = new QDnsLookup(QDnsLookup::SRV, ms_srv_hostname, this);
connect(ms_dns, SIGNAL(finished()), this, SLOT(on_srv_lookup()));
ms_dns->lookup();
#endif
}
void NetworkManager::on_srv_lookup()
{
#ifdef MS_FAILOVER_SUPPORTED
bool connected = false;
if (ms_dns->error() != QDnsLookup::NoError)
{
qWarning("SRV lookup of the master server DNS failed.");
ms_dns->deleteLater();
}
else
{
const auto srv_records = ms_dns->serviceRecords();
for (const QDnsServiceRecord &record : srv_records)
{
qDebug() << "Connecting to " << record.target() << ":" << record.port();
ms_socket->connectToHost(record.target(), record.port());
QTime timer;
timer.start();
do
{
ao_app->processEvents();
if (ms_socket->state() == QAbstractSocket::ConnectedState)
{
connected = true;
break;
}
else if (ms_socket->state() != QAbstractSocket::ConnectingState
&& ms_socket->state() != QAbstractSocket::HostLookupState
&& ms_socket->error() != -1)
{
qDebug() << ms_socket->error();
qWarning() << "Error connecting to master server:" << ms_socket->errorString();
ms_socket->abort();
ms_socket->close();
break;
}
} while (timer.elapsed() < timeout_milliseconds); // Very expensive spin-wait loop - it will bring CPU to 100%!
if (connected)
{
// Connect a one-shot signal in case the master server disconnects randomly
QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError)));
break;
}
else
{
ms_socket->abort();
ms_socket->close();
}
}
}
// Failover to non-SRV connection
if (!connected)
connect_to_master_nosrv();
else
emit ms_connect_finished(connected, false);
#endif
}
void NetworkManager::on_ms_nosrv_connect_success()
{
emit ms_connect_finished(true, false);
QObject::disconnect(ms_socket, SIGNAL(connected()),
this, SLOT(on_ms_nosrv_connect_success()));
QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError)));
}
void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error)
{
qWarning() << "Master server socket error:" << ms_socket->errorString()
<< "(" << error << ")";
// Disconnect the one-shot signal - this way, failover connect attempts
// don't trigger a full retry
QObject::disconnect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError)));
emit ms_connect_finished(false, true);
ms_reconnect_timer->start(ms_reconnect_delay_ms);
}
void NetworkManager::retry_ms_connect()
{
if (!ms_reconnect_timer->isActive() && ms_socket->state() != QAbstractSocket::ConnectingState)
connect_to_master();
}
void NetworkManager::handle_server_packet()
{
char buffer[16384] = {0};
server_socket->read(buffer, server_socket->bytesAvailable());
QString in_data = buffer;
if (!in_data.endsWith("%"))
{
partial_packet = true;
temp_packet += in_data;
return;
}
else
{
if (partial_packet)
{
in_data = temp_packet + in_data;
temp_packet = "";
partial_packet = false;
}
}
QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts));
for (QString packet : packet_list)
{
AOPacket *f_packet = new AOPacket(packet);
ao_app->server_packet_received(f_packet);
}
}