Feat: Support multiple server IP addresses (#3665)

This commit is contained in:
Ashutosh0x 2026-01-17 13:35:32 +05:30 committed by Chris Rizzitello
parent e5e53c02b5
commit 50e7b3aadf
4 changed files with 87 additions and 25 deletions

View file

@ -79,6 +79,11 @@ Client::~Client()
delete m_socketFactory; delete m_socketFactory;
} }
void Client::setServerAddress(const NetworkAddress &address)
{
m_serverAddress = address;
}
void Client::connect(size_t addressIndex) void Client::connect(size_t addressIndex)
{ {
if (m_stream != nullptr) { if (m_stream != nullptr) {

View file

@ -77,6 +77,7 @@ public:
the client is trying to connect or is already connected. the client is trying to connect or is already connected.
*/ */
void connect(size_t addressIndex = 0); void connect(size_t addressIndex = 0);
void setServerAddress(const NetworkAddress &address);
//! Disconnect //! Disconnect
/*! /*!

View file

@ -54,22 +54,45 @@ ClientApp::ClientApp(IEventQueue *events, const QString &processName) : App(even
void ClientApp::parseArgs() void ClientApp::parseArgs()
{ {
// save server address // save server addresses (comma-separated list supported)
if (const auto address = Settings::value(Settings::Client::RemoteHost).toString(); !address.isEmpty()) { if (const auto addressList = Settings::value(Settings::Client::RemoteHost).toString(); !addressList.isEmpty()) {
const int port = Settings::value(Settings::Core::Port).toInt();
const QStringList addresses = addressList.split(',', Qt::SkipEmptyParts);
for (const QString &addr : addresses) {
const QString trimmedAddr = addr.trimmed();
if (trimmedAddr.isEmpty()) {
continue;
}
try { try {
*m_serverAddress = NetworkAddress(address.toStdString(), Settings::value(Settings::Core::Port).toInt()); NetworkAddress netAddr(trimmedAddr.toStdString(), port);
m_serverAddress->resolve(); netAddr.resolve();
m_serverAddresses.push_back(std::move(netAddr));
LOG_DEBUG("added server address: %s", qPrintable(trimmedAddr));
} catch (SocketAddressException &e) { } catch (SocketAddressException &e) {
// allow an address that we can't look up if we're restartable. // allow an address that we can't look up if we're restartable.
// we'll try to resolve the address each time we connect to the // we'll try to resolve the address each time we connect to the
// server. a bad port will never get better. patch by Brent // server. a bad port will never get better.
// Priddy.
if (e.getError() == SocketAddressException::SocketError::BadPort) { if (e.getError() == SocketAddressException::SocketError::BadPort) {
LOG_CRIT("%s: %s" BYE, qPrintable(processName()), e.what(), qPrintable(processName())); LOG_CRIT("%s: %s" BYE, qPrintable(processName()), e.what(), qPrintable(processName()));
bye(s_exitFailed); bye(s_exitFailed);
} else {
// Still add it - we'll try to resolve later
NetworkAddress netAddr(trimmedAddr.toStdString(), port);
m_serverAddresses.push_back(std::move(netAddr));
LOG_WARN("could not resolve address '%s': %s (will retry later)", qPrintable(trimmedAddr), e.what());
} }
} }
} }
if (m_serverAddresses.isEmpty()) {
LOG_CRIT("%s: no valid server addresses specified" BYE, qPrintable(processName()), qPrintable(processName()));
bye(s_exitFailed);
}
LOG_NOTE("configured %zu server address(es)", static_cast<size_t>(m_serverAddresses.size()));
}
} }
const char *ClientApp::daemonName() const const char *ClientApp::daemonName() const
@ -158,23 +181,39 @@ void ClientApp::scheduleClientRestart(double retryTime)
getEvents()->addHandler(EventTypes::Timer, timer, [this, timer](const auto &e) { handleClientRestart(e, timer); }); getEvents()->addHandler(EventTypes::Timer, timer, [this, timer](const auto &e) { handleClientRestart(e, timer); });
} }
void ClientApp::handleClientConnected() const void ClientApp::handleClientConnected()
{ {
LOG_IPC("connected to server"); LOG_IPC("connected to server");
// Reset server index on successful connection
m_currentServerIndex = 0;
m_lastServerAddressIndex = 0;
} }
void ClientApp::handleClientFailed(const Event &e) void ClientApp::handleClientFailed(const Event &e)
{ {
if ((++m_lastServerAddressIndex) < m_client->getLastResolvedAddressesCount()) { if ((++m_lastServerAddressIndex) < m_client->getLastResolvedAddressesCount()) {
// Try next resolved address for current hostname
std::unique_ptr<Client::FailInfo> info(static_cast<Client::FailInfo *>(e.getData())); std::unique_ptr<Client::FailInfo> info(static_cast<Client::FailInfo *>(e.getData()));
LOG_WARN("failed to connect to server=%s, trying next address", info->m_what.c_str()); LOG_WARN("failed to connect to server=%s, trying next resolved address", info->m_what.c_str());
if (!m_suspended) { if (!m_suspended) {
scheduleClientRestart(s_retryTime); scheduleClientRestart(s_retryTime);
} }
} else { } else {
// All resolved addresses exhausted, try next server in list
m_lastServerAddressIndex = 0; m_lastServerAddressIndex = 0;
tryNextServer();
if (m_currentServerIndex == 0) {
// We've cycled through all servers, treat as refused
handleClientRefused(e); handleClientRefused(e);
} else {
std::unique_ptr<Client::FailInfo> info(static_cast<Client::FailInfo *>(e.getData()));
LOG_WARN("failed to connect to server=%s, trying next server in list", info->m_what.c_str());
if (!m_suspended) {
scheduleClientRestart(s_retryTime);
}
}
} }
} }
@ -248,12 +287,13 @@ bool ClientApp::startClient()
if (m_clientScreen == nullptr) { if (m_clientScreen == nullptr) {
clientScreen = openClientScreen(); clientScreen = openClientScreen();
m_client = openClient( m_client = openClient(
Settings::value(Settings::Core::ScreenName).toString().toStdString(), *m_serverAddress, clientScreen Settings::value(Settings::Core::ScreenName).toString().toStdString(), getCurrentServerAddress(), clientScreen
); );
m_clientScreen = clientScreen; m_clientScreen = clientScreen;
LOG_NOTE("started client"); LOG_NOTE("started client");
} }
m_client->setServerAddress(getCurrentServerAddress());
m_client->connect(m_lastServerAddressIndex); m_client->connect(m_lastServerAddressIndex);
return true; return true;
@ -325,22 +365,33 @@ int ClientApp::start()
int ClientApp::runInner(StartupFunc startup) int ClientApp::runInner(StartupFunc startup)
{ {
// general initialization
m_serverAddress = new NetworkAddress;
int result; int result;
try { try {
// run // run
result = startup(); result = startup();
} catch (...) { } catch (...) {
delete m_serverAddress;
throw; throw;
} }
return result; return result;
} }
NetworkAddress &ClientApp::getCurrentServerAddress()
{
if (m_serverAddresses.isEmpty()) {
throw std::runtime_error("No server addresses configured");
}
return m_serverAddresses[m_currentServerIndex];
}
void ClientApp::tryNextServer()
{
if (m_serverAddresses.size() > 1) {
m_currentServerIndex = (m_currentServerIndex + 1) % m_serverAddresses.size();
LOG_DEBUG("switching to server %zu of %zu", m_currentServerIndex + 1, m_serverAddresses.size());
}
}
void ClientApp::startNode() void ClientApp::startNode()
{ {
// start the client. if this return false then we've failed and // start the client. if this return false then we've failed and

View file

@ -9,6 +9,9 @@
#pragma once #pragma once
#include "deskflow/App.h" #include "deskflow/App.h"
#include "net/NetworkAddress.h"
#include <QList>
namespace deskflow { namespace deskflow {
class Screen; class Screen;
@ -17,7 +20,6 @@ class ClientArgs;
class Event; class Event;
class Client; class Client;
class NetworkAddress;
class Thread; class Thread;
class ISocketFactory; class ISocketFactory;
@ -54,7 +56,7 @@ public:
void closeClientScreen(deskflow::Screen *screen); void closeClientScreen(deskflow::Screen *screen);
void handleClientRestart(const Event &, EventQueueTimer *vtimer); void handleClientRestart(const Event &, EventQueueTimer *vtimer);
void scheduleClientRestart(double retryTime); void scheduleClientRestart(double retryTime);
void handleClientConnected() const; void handleClientConnected();
void handleClientFailed(const Event &e); void handleClientFailed(const Event &e);
void handleClientRefused(const Event &e); void handleClientRefused(const Event &e);
void handleClientDisconnected(); void handleClientDisconnected();
@ -78,10 +80,13 @@ public:
private: private:
ISocketFactory *getSocketFactory() const; ISocketFactory *getSocketFactory() const;
NetworkAddress &getCurrentServerAddress();
void tryNextServer();
bool m_suspended = false; bool m_suspended = false;
Client *m_client = nullptr; Client *m_client = nullptr;
deskflow::Screen *m_clientScreen = nullptr; deskflow::Screen *m_clientScreen = nullptr;
NetworkAddress *m_serverAddress = nullptr; QList<NetworkAddress> m_serverAddresses;
size_t m_currentServerIndex = 0;
size_t m_lastServerAddressIndex = 0; size_t m_lastServerAddressIndex = 0;
}; };