1584 lines
43 KiB
C++
1584 lines
43 KiB
C++
|
// NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||
|
// Copyright (C) 2010 Winch Gate Property Limited
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Affero General Public License as
|
||
|
// published by the Free Software Foundation, either version 3 of the
|
||
|
// License, or (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Affero General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Affero General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include "config.h"
|
||
|
#endif // HAVE_CONFIG_H
|
||
|
|
||
|
#ifndef NELNS_CONFIG
|
||
|
#define NELNS_CONFIG ""
|
||
|
#endif // NELNS_CONFIG
|
||
|
|
||
|
#ifndef NELNS_LOGS
|
||
|
#define NELNS_LOGS ""
|
||
|
#endif // NELNS_LOGS
|
||
|
|
||
|
#include "nel/misc/types_nl.h"
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <ctype.h>
|
||
|
#include <math.h>
|
||
|
|
||
|
#include <list>
|
||
|
|
||
|
#include "nel/misc/debug.h"
|
||
|
#include "nel/misc/config_file.h"
|
||
|
#include "nel/misc/displayer.h"
|
||
|
#include "nel/misc/command.h"
|
||
|
#include "nel/misc/variable.h"
|
||
|
#include "nel/misc/log.h"
|
||
|
#include "nel/misc/file.h"
|
||
|
#include "nel/misc/path.h"
|
||
|
|
||
|
#include "nel/net/service.h"
|
||
|
#include "nel/net/unified_network.h"
|
||
|
#include "nel/net/login_cookie.h"
|
||
|
|
||
|
#include "welcome_service.h"
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace NLMISC;
|
||
|
using namespace NLNET;
|
||
|
using namespace WS;
|
||
|
|
||
|
|
||
|
CVariable<sint> PlayerLimit(
|
||
|
"ws","PlayerLimit", "Rough max number of players accepted on this shard (-1 for Unlimited)",
|
||
|
5000,
|
||
|
0, true );
|
||
|
|
||
|
// Forward declaration of callback cbShardOpen (see ShardOpen variable)
|
||
|
void cbShardOpen(IVariable &var);
|
||
|
|
||
|
// Forward declaration of callback cbShardOpenStateFile (see ShardOpenStateFile variable)
|
||
|
void cbShardOpenStateFile(IVariable &var);
|
||
|
|
||
|
// Forward declaration of callback cbUsePatchMode
|
||
|
void cbUsePatchMode(IVariable &var);
|
||
|
|
||
|
// Types of open state
|
||
|
enum TShardOpenState
|
||
|
{
|
||
|
ClosedForAll = 0,
|
||
|
OpenOnlyForAllowed = 1,
|
||
|
OpenForAll = 2
|
||
|
};
|
||
|
|
||
|
static bool AllowDispatchMsgToLS = false;
|
||
|
|
||
|
/**
|
||
|
* ShardOpen
|
||
|
* true if shard is open to public
|
||
|
* 0 means closed for all but :DEV:
|
||
|
* 1 means open only for groups in config file (see OpenGroups variable) and :DEV:
|
||
|
* 2 means open for all
|
||
|
*/
|
||
|
CVariable<uint> ShardOpen("ws", "ShardOpen", "Indicates if shard is open to public (0 closed for all but :DEV:, 1 open only for groups in cfg, 2 open for all)", 2, 0, true, cbShardOpen);
|
||
|
|
||
|
/**
|
||
|
* ShardOpenStateFile
|
||
|
* true if shard is open to public
|
||
|
*/
|
||
|
CVariable<string> ShardOpenStateFile("ws", "ShardOpenStateFile", "Name of the file that contains ShardOpen state", "", 0, true, cbShardOpenStateFile);
|
||
|
|
||
|
/**
|
||
|
* OpenGroups
|
||
|
*/
|
||
|
CVariable<string> OpenGroups("ws", "OpenGroups", "list of groups allowed at ShardOpen Level 1", "", 0, true);
|
||
|
|
||
|
/**
|
||
|
* OpenFrontEndThreshold
|
||
|
* The FS balance algorithm works like this:
|
||
|
* - select the least loaded frontend
|
||
|
* - if this frontend has more than the OpenFrontEndThreshold
|
||
|
* - try to open a new frontend
|
||
|
* - reselect least loaded frontend
|
||
|
*/
|
||
|
CVariable<uint> OpenFrontEndThreshold("ws", "OpenFrontEndThreshold", "Limit number of players on all FS to decide to open a new FS", 800, 0, true );
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Use Patch mode
|
||
|
*/
|
||
|
CVariable<bool> UsePatchMode("ws", "UsePatchMode", "Use Frontends as Patch servers (at FS startup)", true, 0, true, cbUsePatchMode );
|
||
|
|
||
|
/**
|
||
|
* Use Patch mode
|
||
|
*/
|
||
|
CVariable<bool> DontUseLS("ws", "DontUseLS", "Dont use the login service", false, 0, true);
|
||
|
|
||
|
|
||
|
// Shortcut to the module instance
|
||
|
//CWelcomeServiceMod *CWelcomeServiceMod::_Instance = NULL;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Using expected services and current running service instances, this class
|
||
|
* reports a main "online status".
|
||
|
*/
|
||
|
class COnlineServices
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
/// Set expected instances. Ex: { "TICKS", "FS", "FS", "FS" }
|
||
|
void setExpectedInstances( CConfigFile::CVar& var )
|
||
|
{
|
||
|
// Reset "expected" counters (but don't clear the map, keep the running instances)
|
||
|
CInstances::iterator ici;
|
||
|
for ( ici=_Instances.begin(); ici!=_Instances.end(); ++ici )
|
||
|
{
|
||
|
(*ici).second.Expected = 0;
|
||
|
}
|
||
|
// Rebuild "expected" counters
|
||
|
for ( uint i=0; i!=var.size(); ++i )
|
||
|
{
|
||
|
++_Instances[var.asString(i)].Expected;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Add a service instance
|
||
|
void addInstance( const std::string& serviceName )
|
||
|
{
|
||
|
++_Instances[serviceName].Running;
|
||
|
}
|
||
|
|
||
|
/// Remove a service instance
|
||
|
void removeInstance( const std::string& serviceName )
|
||
|
{
|
||
|
CInstances::iterator ici = _Instances.find( serviceName );
|
||
|
if ( ici != _Instances.end() )
|
||
|
{
|
||
|
--(*ici).second.Running;
|
||
|
|
||
|
// Remove from the map only if not part of the expected list
|
||
|
if ( ((*ici).second.Expected == 0) && ((*ici).second.Running == 0) )
|
||
|
{
|
||
|
_Instances.erase( ici );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nlwarning( "Can't remove instance of %s", serviceName.c_str() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Check if all expected instances are online
|
||
|
bool getOnlineStatus() const
|
||
|
{
|
||
|
CInstances::const_iterator ici;
|
||
|
for ( ici=_Instances.begin(); ici!=_Instances.end(); ++ici )
|
||
|
{
|
||
|
if ( ! ici->second.isOnlineAsExpected() )
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// Display contents
|
||
|
void display( NLMISC::CLog& log = *NLMISC::DebugLog )
|
||
|
{
|
||
|
CInstances::const_iterator ici;
|
||
|
for ( ici=_Instances.begin(); ici!=_Instances.end(); ++ici )
|
||
|
{
|
||
|
log.displayNL( "%s: %s (%u expected, %u running)",
|
||
|
(*ici).first.c_str(),
|
||
|
(*ici).second.Expected ? ((*ici).second.isOnlineAsExpected() ? "ONLINE" : "MISSING") : "OPTIONAL",
|
||
|
(*ici).second.Expected, (*ici).second.Running );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
|
||
|
struct TInstanceCounters
|
||
|
{
|
||
|
TInstanceCounters() : Expected(0), Running(0) {}
|
||
|
|
||
|
// If not expected, count as online as well
|
||
|
bool isOnlineAsExpected() const { return Running >= Expected; }
|
||
|
|
||
|
uint Expected;
|
||
|
uint Running;
|
||
|
};
|
||
|
|
||
|
typedef std::map< std::string, TInstanceCounters > CInstances;
|
||
|
|
||
|
CInstances _Instances;
|
||
|
};
|
||
|
|
||
|
/// Online services
|
||
|
COnlineServices OnlineServices;
|
||
|
|
||
|
|
||
|
/// Main online status
|
||
|
bool OnlineStatus;
|
||
|
|
||
|
/// Send changes of status to the LS
|
||
|
void reportOnlineStatus( bool newStatus )
|
||
|
{
|
||
|
if ( newStatus != OnlineStatus && AllowDispatchMsgToLS )
|
||
|
{
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
CMessage msgout( "OL_ST" );
|
||
|
msgout.serial( newStatus );
|
||
|
CUnifiedNetwork::getInstance()->send( "LS", msgout );
|
||
|
}
|
||
|
|
||
|
if (CWelcomeServiceMod::isInitialized())
|
||
|
{
|
||
|
// send a status report to welcome service client
|
||
|
CWelcomeServiceMod::getInstance()->reportWSOpenState(newStatus);
|
||
|
}
|
||
|
|
||
|
OnlineStatus = newStatus;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/// Set the version of the shard. you have to increase it each time the client-server protocol changes.
|
||
|
/// You have to increment the client too (the server and client version must be the same to run correctly)
|
||
|
static const uint32 ServerVersion = 1;
|
||
|
|
||
|
/// Contains the correspondance between userid and the FES connection where the userid is connected.
|
||
|
map<uint32, TServiceId> UserIdSockAssociations;
|
||
|
|
||
|
// ubi hack
|
||
|
string FrontEndAddress;
|
||
|
|
||
|
|
||
|
|
||
|
enum TFESState
|
||
|
{
|
||
|
PatchOnly,
|
||
|
AcceptClientOnly
|
||
|
};
|
||
|
|
||
|
struct CFES
|
||
|
{
|
||
|
CFES (TServiceId sid) : SId(sid), NbPendingUsers(0), NbUser(0), State(PatchOnly) { }
|
||
|
|
||
|
TServiceId SId; // Connection to the front end
|
||
|
uint32 NbPendingUsers; // Number of not yet connected users (but rooted to this frontend)
|
||
|
uint32 NbUser; // Number of user currently connected on this front end
|
||
|
|
||
|
TFESState State; // State of frontend (patching/accepting clients)
|
||
|
std::string PatchAddress; // Address of frontend patching server
|
||
|
|
||
|
uint32 getUsersCountHeuristic() const
|
||
|
{
|
||
|
return NbUser + NbPendingUsers;
|
||
|
}
|
||
|
|
||
|
void setToAcceptClients()
|
||
|
{
|
||
|
if (State == AcceptClientOnly)
|
||
|
return;
|
||
|
|
||
|
// tell FS to accept client
|
||
|
State = AcceptClientOnly;
|
||
|
CMessage msgOpenFES("FS_ACCEPT");
|
||
|
CUnifiedNetwork::getInstance()->send(SId, msgOpenFES);
|
||
|
|
||
|
// report state to LS
|
||
|
bool dummy;
|
||
|
reportStateToLS(dummy, true);
|
||
|
}
|
||
|
|
||
|
void reportStateToLS(bool& reportPatching, bool alive = true)
|
||
|
{
|
||
|
// report to LS
|
||
|
|
||
|
bool patching = (State == PatchOnly);
|
||
|
if (alive && patching)
|
||
|
reportPatching = true;
|
||
|
|
||
|
if ( AllowDispatchMsgToLS )
|
||
|
{
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
CMessage msgout("REPORT_FS_STATE");
|
||
|
msgout.serial(SId);
|
||
|
msgout.serial(alive);
|
||
|
msgout.serial(patching);
|
||
|
msgout.serial(PatchAddress);
|
||
|
CUnifiedNetwork::getInstance()->send("LS", msgout);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
list<CFES> FESList;
|
||
|
|
||
|
/*
|
||
|
* Find the best front end service for a new connecting user (return NULL if there is no suitable FES).
|
||
|
* Additionally, calculate totalNbUsers.
|
||
|
*/
|
||
|
CFES *findBestFES ( uint& totalNbUsers )
|
||
|
{
|
||
|
totalNbUsers = 0;
|
||
|
|
||
|
CFES* best = NULL;
|
||
|
|
||
|
for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
|
||
|
{
|
||
|
CFES &fes = *it;
|
||
|
if (fes.State == AcceptClientOnly)
|
||
|
{
|
||
|
if (best == NULL || best->getUsersCountHeuristic() > fes.getUsersCountHeuristic())
|
||
|
best = &fes;
|
||
|
|
||
|
totalNbUsers += fes.NbUser;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return best;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Select a frontend in patch mode to open
|
||
|
* Returns true if a new FES was open, false if no FES could be open
|
||
|
*/
|
||
|
bool openNewFES()
|
||
|
{
|
||
|
for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
|
||
|
{
|
||
|
if ((*it).State == PatchOnly)
|
||
|
{
|
||
|
nlinfo("openNewFES: ask the FS %d to accept clients", it->SId.get());
|
||
|
|
||
|
// switch FES to AcceptClientOnly
|
||
|
(*it).setToAcceptClients();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void displayFES ()
|
||
|
{
|
||
|
nlinfo ("There's %d FES in the list:", FESList.size());
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
nlinfo(" > %u NbUser:%d NbPendingUser:%d", it->SId.get(), it->NbUser, it->NbPendingUsers);
|
||
|
}
|
||
|
nlinfo ("End of the list");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//////////////////// CONNECTION TO THE FRONT END SERVICE ///////////////////////////////////////////////////////
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void cbFESShardChooseShard (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
// the WS answer a user authorize
|
||
|
string reason;
|
||
|
CLoginCookie cookie;
|
||
|
string addr;
|
||
|
|
||
|
//
|
||
|
// S09: receive "SCS" message from FES and send the "SCS" message to the LS
|
||
|
//
|
||
|
|
||
|
CMessage msgout ("SCS");
|
||
|
|
||
|
msgin.serial (reason);
|
||
|
msgout.serial (reason);
|
||
|
|
||
|
msgin.serial (cookie);
|
||
|
msgout.serial (cookie);
|
||
|
|
||
|
if (reason.empty())
|
||
|
{
|
||
|
msgin.serial (addr);
|
||
|
|
||
|
// if we set the FontEndAddress in the welcome_service.cfg we use this address
|
||
|
if (FrontEndAddress.empty())
|
||
|
{
|
||
|
msgout.serial (addr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
msgout.serial (FrontEndAddress);
|
||
|
}
|
||
|
|
||
|
uint32 nbPendingUser;
|
||
|
msgin.serial(nbPendingUser);
|
||
|
|
||
|
// update the pending user count for this shard
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
if (it->SId == sid)
|
||
|
{
|
||
|
it->NbPendingUsers = nbPendingUser;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
// OBSOLETE: LS doesn't read patching URLs
|
||
|
// build patch server list
|
||
|
std::string PatchURLS;
|
||
|
for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
|
||
|
{
|
||
|
if ((*it).State == PatchOnly && !(*it).PatchAddress.empty())
|
||
|
{
|
||
|
if (!PatchURLS.empty())
|
||
|
PatchURLS += '|';
|
||
|
PatchURLS += (*it).PatchAddress;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
msgout.serial(PatchURLS);
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
if (PendingFeResponse.find(cookie) != PendingFeResponse.end())
|
||
|
{
|
||
|
nldebug( "ERLOG: SCS recvd from %s-%hu => sending %s to SU", serviceName.c_str(), sid.get(), cookie.toString().c_str());
|
||
|
|
||
|
// this response is not waited by LS
|
||
|
TPendingFEResponseInfo &pfri = PendingFeResponse.find(cookie)->second;
|
||
|
|
||
|
pfri.WSMod->frontendResponse(pfri.WaiterModule, pfri.UserId, reason, cookie, addr);
|
||
|
// cleanup pending record
|
||
|
PendingFeResponse.erase(cookie);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nldebug( "ERLOG: SCS recvd from %s-%hu, but pending %s not found", serviceName.c_str(), sid.get(), cookie.toString().c_str());
|
||
|
|
||
|
// return the result to the LS
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
CUnifiedNetwork::getInstance()->send ("LS", msgout);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// This function is call when a FES accepted a new client or lost a connection to a client
|
||
|
void cbFESClientConnected (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
//
|
||
|
// S15: receive "CC" message from FES and send "CC" message to the "LS"
|
||
|
//
|
||
|
|
||
|
CMessage msgout ("CC");
|
||
|
|
||
|
uint32 userid;
|
||
|
msgin.serial (userid);
|
||
|
msgout.serial (userid);
|
||
|
|
||
|
uint8 con;
|
||
|
msgin.serial (con);
|
||
|
msgout.serial (con);
|
||
|
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
CUnifiedNetwork::getInstance()->send ("LS", msgout);
|
||
|
}
|
||
|
|
||
|
// add or remove the user number really connected on this shard
|
||
|
uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
if (it->SId == sid)
|
||
|
{
|
||
|
if (con)
|
||
|
{
|
||
|
(*it).NbUser++;
|
||
|
|
||
|
// the client connected, it's no longer pending
|
||
|
if ((*it).NbPendingUsers > 0)
|
||
|
(*it).NbPendingUsers--;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( (*it).NbUser != 0 )
|
||
|
(*it).NbUser--;
|
||
|
}
|
||
|
}
|
||
|
totalNbOnlineUsers += (*it).NbUser;
|
||
|
totalNbPendingUsers += (*it).NbPendingUsers;
|
||
|
}
|
||
|
|
||
|
if (CWelcomeServiceMod::isInitialized())
|
||
|
CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
|
||
|
|
||
|
if (con)
|
||
|
{
|
||
|
// we know that this user is on this FES
|
||
|
UserIdSockAssociations.insert (make_pair (userid, sid));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// remove the user
|
||
|
UserIdSockAssociations.erase (userid);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// This function is called when a FES rejected a client' cookie
|
||
|
void cbFESRemovedPendingCookie(CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
CLoginCookie cookie;
|
||
|
msgin.serial(cookie);
|
||
|
nldebug( "ERLOG: RPC recvd from %s-%hu => %s removed", serviceName.c_str(), sid.get(), cookie.toString().c_str(), cookie.toString().c_str());
|
||
|
|
||
|
|
||
|
// client' cookie rejected, no longer pending
|
||
|
uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
if ((*it).SId == sid)
|
||
|
{
|
||
|
if ((*it).NbPendingUsers > 0)
|
||
|
--(*it).NbPendingUsers;
|
||
|
}
|
||
|
totalNbOnlineUsers += (*it).NbUser;
|
||
|
totalNbPendingUsers += (*it).NbPendingUsers;
|
||
|
}
|
||
|
|
||
|
if (CWelcomeServiceMod::isInitialized())
|
||
|
{
|
||
|
CWelcomeServiceMod::getInstance()->pendingUserLost(cookie);
|
||
|
CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function is called by FES to setup its PatchAddress
|
||
|
void cbFESPatchAddress(CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
std::string address;
|
||
|
msgin.serial(address);
|
||
|
|
||
|
bool acceptClients;
|
||
|
msgin.serial(acceptClients);
|
||
|
|
||
|
nldebug("Received patch server address '%s' from service %s %d", address.c_str(), serviceName.c_str(), sid.get());
|
||
|
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
if ((*it).SId == sid)
|
||
|
{
|
||
|
nldebug("Affected patch server address '%s' to frontend %s %d", address.c_str(), serviceName.c_str(), sid.get());
|
||
|
|
||
|
if (!UsePatchMode.get() && !acceptClients)
|
||
|
{
|
||
|
// not in patch mode, force fs to accept clients
|
||
|
acceptClients = true;
|
||
|
(*it).setToAcceptClients();
|
||
|
}
|
||
|
|
||
|
(*it).PatchAddress = address;
|
||
|
(*it).State = (acceptClients ? AcceptClientOnly : PatchOnly);
|
||
|
if (acceptClients)
|
||
|
nldebug("Frontend %s %d reported to accept client, patching unavailable for that server", address.c_str(), serviceName.c_str(), sid.get());
|
||
|
else
|
||
|
nldebug("Frontend %s %d reported to be in patching mode", address.c_str(), serviceName.c_str(), sid.get());
|
||
|
|
||
|
bool dummy;
|
||
|
(*it).reportStateToLS(dummy);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function is called by FES to setup the right number of players (if FES was already present before WS launching)
|
||
|
void cbFESNbPlayers(CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
// *********** WARNING *******************
|
||
|
// This version of the callback is deprecated, the system
|
||
|
// now use cbFESNbPlayers2 that report the pending user count
|
||
|
// as well as the number of connected players.
|
||
|
// It is kept for backward compatibility only.
|
||
|
// ***************************************
|
||
|
|
||
|
uint32 nbPlayers;
|
||
|
msgin.serial(nbPlayers);
|
||
|
|
||
|
uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
if ((*it).SId == sid)
|
||
|
{
|
||
|
nldebug("Frontend '%d' reported %d online users", sid.get(), nbPlayers);
|
||
|
(*it).NbUser = nbPlayers;
|
||
|
if (nbPlayers != 0 && (*it).State == PatchOnly)
|
||
|
{
|
||
|
nlwarning("Frontend %d is in state PatchOnly, yet reports to have online %d players, state AcceptClientOnly is forced (FS_ACCEPT message sent)");
|
||
|
(*it).setToAcceptClients();
|
||
|
}
|
||
|
}
|
||
|
totalNbOnlineUsers += (*it).NbUser;
|
||
|
totalNbPendingUsers += (*it).NbPendingUsers;
|
||
|
}
|
||
|
|
||
|
if (CWelcomeServiceMod::isInitialized())
|
||
|
CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
|
||
|
}
|
||
|
|
||
|
|
||
|
// This function is called by FES to setup the right number of players (if FES was already present before WS launching)
|
||
|
void cbFESNbPlayers2(CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
uint32 nbPlayers;
|
||
|
uint32 nbPendingPlayers;
|
||
|
msgin.serial(nbPlayers);
|
||
|
msgin.serial(nbPendingPlayers);
|
||
|
|
||
|
uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
CFES &fes = *it;
|
||
|
if (fes.SId == sid)
|
||
|
{
|
||
|
nldebug("Frontend '%d' reported %d online users", sid.get(), nbPlayers);
|
||
|
fes.NbUser = nbPlayers;
|
||
|
fes.NbPendingUsers = nbPendingPlayers;
|
||
|
if (nbPlayers != 0 && fes.State == PatchOnly)
|
||
|
{
|
||
|
nlwarning("Frontend %d is in state PatchOnly, yet reports to have online %d players, state AcceptClientOnly is forced (FS_ACCEPT message sent)");
|
||
|
(*it).setToAcceptClients();
|
||
|
}
|
||
|
}
|
||
|
totalNbOnlineUsers += fes.NbUser;
|
||
|
totalNbPendingUsers += fes.NbPendingUsers;
|
||
|
}
|
||
|
|
||
|
if (CWelcomeServiceMod::isInitialized())
|
||
|
CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set Shard open state
|
||
|
*/
|
||
|
void setShardOpenState(TShardOpenState state, bool writeInVar = true)
|
||
|
{
|
||
|
if (writeInVar)
|
||
|
ShardOpen = state;
|
||
|
|
||
|
if ( AllowDispatchMsgToLS )
|
||
|
{
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
// send to LS current shard state
|
||
|
CMessage msgout ("SET_SHARD_OPEN");
|
||
|
uint8 shardOpenState = (uint8)state;
|
||
|
|
||
|
msgout.serial (shardOpenState);
|
||
|
CUnifiedNetwork::getInstance()->send ("LS", msgout);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Set Shard Open State
|
||
|
* uint8 Open State (0 closed for all, 1 open for groups in cfg, 2 open for all)
|
||
|
*/
|
||
|
void cbSetShardOpen(CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
uint8 shardOpenState;
|
||
|
msgin.serial (shardOpenState);
|
||
|
|
||
|
if (shardOpenState > OpenForAll)
|
||
|
{
|
||
|
shardOpenState = OpenForAll;
|
||
|
}
|
||
|
|
||
|
setShardOpenState((TShardOpenState)shardOpenState);
|
||
|
}
|
||
|
|
||
|
// forward declaration to callback
|
||
|
void cbShardOpenStateFile(IVariable &var);
|
||
|
|
||
|
/*
|
||
|
* Restore Shard Open state from config file or from file if found
|
||
|
*/
|
||
|
void cbRestoreShardOpen(CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
// first restore state from config file
|
||
|
CConfigFile::CVar* var = IService::getInstance()->ConfigFile.getVarPtr("ShardOpen");
|
||
|
if (var != NULL)
|
||
|
{
|
||
|
setShardOpenState((TShardOpenState)var->asInt());
|
||
|
}
|
||
|
|
||
|
// then restore state from state file, if it exists
|
||
|
cbShardOpenStateFile(ShardOpenStateFile);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// a new front end connecting to me, add it
|
||
|
void cbFESConnection (const std::string &serviceName, TServiceId sid, void *arg)
|
||
|
{
|
||
|
FESList.push_back (CFES ((TServiceId)sid));
|
||
|
nldebug("new FES connection: sid %u", sid.get());
|
||
|
displayFES ();
|
||
|
|
||
|
bool dummy;
|
||
|
FESList.back().reportStateToLS(dummy);
|
||
|
|
||
|
if (!UsePatchMode.get())
|
||
|
{
|
||
|
FESList.back().setToAcceptClients();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// a front end closes the connection, deconnect him
|
||
|
void cbFESDisconnection (const std::string &serviceName, TServiceId sid, void *arg)
|
||
|
{
|
||
|
nldebug("new FES disconnection: sid %u", sid.get());
|
||
|
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
if ((*it).SId == sid)
|
||
|
{
|
||
|
// send a message to the LS to say that all players from this FES are offline
|
||
|
map<uint32, TServiceId>::iterator itc = UserIdSockAssociations.begin();
|
||
|
map<uint32, TServiceId>::iterator nitc = itc;
|
||
|
while (itc != UserIdSockAssociations.end())
|
||
|
{
|
||
|
nitc++;
|
||
|
if ((*itc).second == sid)
|
||
|
{
|
||
|
// bye bye little player
|
||
|
uint32 userid = (*itc).first;
|
||
|
nlinfo ("Due to a frontend crash, removed the player %d", userid);
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
CMessage msgout ("CC");
|
||
|
msgout.serial (userid);
|
||
|
uint8 con = 0;
|
||
|
msgout.serial (con);
|
||
|
CUnifiedNetwork::getInstance()->send ("LS", msgout);
|
||
|
}
|
||
|
UserIdSockAssociations.erase (itc);
|
||
|
}
|
||
|
itc = nitc;
|
||
|
}
|
||
|
|
||
|
bool dummy;
|
||
|
(*it).reportStateToLS(dummy, false);
|
||
|
|
||
|
// remove the FES
|
||
|
FESList.erase (it);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update the welcome service client with the new count of connection
|
||
|
|
||
|
uint32 totalNbOnlineUsers =0, totalNbPendingUsers = 0;
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end(); it++)
|
||
|
{
|
||
|
const CFES &fes = *it;
|
||
|
totalNbOnlineUsers += fes.NbUser;
|
||
|
totalNbPendingUsers += fes.NbPendingUsers;
|
||
|
}
|
||
|
|
||
|
if (CWelcomeServiceMod::isInitialized())
|
||
|
CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
|
||
|
|
||
|
displayFES ();
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
void cbServiceUp (const std::string &serviceName, TServiceId sid, void *arg)
|
||
|
{
|
||
|
OnlineServices.addInstance( serviceName );
|
||
|
bool online = OnlineServices.getOnlineStatus();
|
||
|
reportOnlineStatus( online );
|
||
|
|
||
|
// send shard id to service
|
||
|
sint32 shardId;
|
||
|
if (IService::getInstance()->haveArg('S'))
|
||
|
{
|
||
|
// use the command line param if set
|
||
|
shardId = atoi(IService::getInstance()->getArg('S').c_str());
|
||
|
}
|
||
|
else if (IService::getInstance()->ConfigFile.exists ("ShardId"))
|
||
|
{
|
||
|
// use the config file param if set
|
||
|
shardId = IService::getInstance()->ConfigFile.getVar ("ShardId").asInt();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
shardId = -1;
|
||
|
}
|
||
|
|
||
|
if (shardId == -1)
|
||
|
{
|
||
|
nlerror ("ShardId variable must be valid (>0)");
|
||
|
}
|
||
|
|
||
|
CMessage msgout("R_SH_ID");
|
||
|
msgout.serial(shardId);
|
||
|
CUnifiedNetwork::getInstance()->send (sid, msgout);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
void cbServiceDown (const std::string &serviceName, TServiceId sid, void *arg)
|
||
|
{
|
||
|
OnlineServices.removeInstance( serviceName );
|
||
|
bool online = OnlineServices.getOnlineStatus();
|
||
|
reportOnlineStatus( online );
|
||
|
}
|
||
|
|
||
|
|
||
|
// Callback Array for message from FES
|
||
|
TUnifiedCallbackItem FESCallbackArray[] =
|
||
|
{
|
||
|
{ "SCS", cbFESShardChooseShard },
|
||
|
{ "CC", cbFESClientConnected },
|
||
|
{ "RPC", cbFESRemovedPendingCookie },
|
||
|
{ "FEPA", cbFESPatchAddress },
|
||
|
{ "NBPLAYERS", cbFESNbPlayers },
|
||
|
{ "NBPLAYERS2", cbFESNbPlayers2 },
|
||
|
|
||
|
{ "SET_SHARD_OPEN", cbSetShardOpen },
|
||
|
{ "RESTORE_SHARD_OPEN", cbRestoreShardOpen },
|
||
|
};
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//////////////////// CONNECTION TO THE LOGIN SERVICE ///////////////////////////////////////////////////////////
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
void cbLSChooseShard (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
// the LS warns me that a new client want to come in my shard
|
||
|
|
||
|
nldebug( "ERLOG: CS recvd from %s-%hu", serviceName.c_str(), sid.get());
|
||
|
|
||
|
//
|
||
|
// S07: receive the "CS" message from LS and send the "CS" message to the selected FES
|
||
|
//
|
||
|
|
||
|
CLoginCookie cookie;
|
||
|
msgin.serial (cookie);
|
||
|
string userName, userPriv, userExtended;
|
||
|
msgin.serial (userName);
|
||
|
|
||
|
try
|
||
|
{
|
||
|
msgin.serial (userPriv);
|
||
|
}
|
||
|
catch (Exception &)
|
||
|
{
|
||
|
nlwarning ("LS didn't give me the user privilege for user '%s', set to empty", userName.c_str());
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
msgin.serial (userExtended);
|
||
|
}
|
||
|
catch (Exception &)
|
||
|
{
|
||
|
nlwarning ("LS didn't give me the extended data for user '%s', set to empty", userName.c_str());
|
||
|
}
|
||
|
|
||
|
|
||
|
string ret = lsChooseShard(userName, cookie, userPriv, userExtended, WS::TUserRole::ur_player, 0xffffffff, ~0);
|
||
|
|
||
|
if (!ret.empty())
|
||
|
{
|
||
|
// send back an error message to LS
|
||
|
CMessage msgout ("SCS");
|
||
|
msgout.serial (ret);
|
||
|
msgout.serial (cookie);
|
||
|
CUnifiedNetwork::getInstance()->send(sid, msgout);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//void cbLSChooseShard (CMessage &msgin, const std::string &serviceName, uint16 sid)
|
||
|
std::string lsChooseShard (const std::string &userName,
|
||
|
const CLoginCookie &cookie,
|
||
|
const std::string &userPriv,
|
||
|
const std::string &userExtended,
|
||
|
WS::TUserRole userRole,
|
||
|
uint32 instanceId,
|
||
|
uint32 charSlot)
|
||
|
{
|
||
|
// the LS warns me that a new client want to come in my shard
|
||
|
|
||
|
//
|
||
|
// S07: receive the "CS" message from LS and send the "CS" message to the selected FES
|
||
|
//
|
||
|
|
||
|
/*
|
||
|
uint totalNbUsers;
|
||
|
CFES *best = findBestFES( totalNbUsers );
|
||
|
if (best == NULL)
|
||
|
{
|
||
|
// answer the LS that we can't accept the user
|
||
|
CMessage msgout ("SCS");
|
||
|
string reason = "No front-end server available";
|
||
|
msgout.serial (reason);
|
||
|
msgout.serial (cookie);
|
||
|
CUnifiedNetwork::getInstance()->send(sid, msgout);
|
||
|
return;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
uint totalNbUsers;
|
||
|
CFES* best = findBestFES( totalNbUsers );
|
||
|
|
||
|
// could not find a good FES or best FES has more players than balance limit
|
||
|
if (best == NULL || best->getUsersCountHeuristic() >= OpenFrontEndThreshold)
|
||
|
{
|
||
|
// open a new frontend
|
||
|
openNewFES();
|
||
|
|
||
|
// reselect best FES (will return newly open FES, or previous if no more FES available)
|
||
|
best = findBestFES(totalNbUsers);
|
||
|
|
||
|
// check there is a FES available
|
||
|
if (best == NULL)
|
||
|
{
|
||
|
// answer the LS that we can't accept the user
|
||
|
return "No front-end server available";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool authorizeUser = false;
|
||
|
bool forceAuthorize = false;
|
||
|
|
||
|
if (userPriv == ":DEV:")
|
||
|
{
|
||
|
// devs have all privileges
|
||
|
authorizeUser = true;
|
||
|
forceAuthorize = true;
|
||
|
}
|
||
|
else if (ShardOpen != ClosedForAll)
|
||
|
{
|
||
|
const std::string& allowedGroups = OpenGroups;
|
||
|
bool userInOpenGroups = (!userPriv.empty() && !allowedGroups.empty() && allowedGroups.find(userPriv) != std::string::npos);
|
||
|
|
||
|
// open for all or user is privileged
|
||
|
authorizeUser = (ShardOpen == OpenForAll || userInOpenGroups);
|
||
|
// let authorized users to force access even if limit is reached
|
||
|
forceAuthorize = userInOpenGroups;
|
||
|
}
|
||
|
|
||
|
bool shardLimitReached = ( (PlayerLimit.get() != -1) && (totalNbUsers >= (uint)PlayerLimit.get()) );
|
||
|
|
||
|
if (!forceAuthorize && (!authorizeUser || shardLimitReached))
|
||
|
{
|
||
|
// answer the LS that we can't accept the user
|
||
|
CMessage msgout ("SCS");
|
||
|
string reason;
|
||
|
if (shardLimitReached)
|
||
|
return "The shard is currently full, please try again in 5 minutes.";
|
||
|
else
|
||
|
return "The shard is closed.";
|
||
|
}
|
||
|
|
||
|
|
||
|
CMessage msgout ("CS");
|
||
|
msgout.serial (const_cast<CLoginCookie&>(cookie));
|
||
|
msgout.serial (const_cast<string&>(userName), const_cast<string&>(userPriv), const_cast<string&>(userExtended));
|
||
|
msgout.serial (instanceId);
|
||
|
msgout.serial (charSlot);
|
||
|
|
||
|
CUnifiedNetwork::getInstance()->send (best->SId, msgout);
|
||
|
best->NbPendingUsers++;
|
||
|
|
||
|
// Update counts
|
||
|
uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
|
||
|
for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
|
||
|
{
|
||
|
totalNbOnlineUsers += (*it).NbUser;
|
||
|
totalNbPendingUsers += (*it).NbPendingUsers;
|
||
|
}
|
||
|
if (CWelcomeServiceMod::isInitialized())
|
||
|
CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
|
||
|
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
void cbFailed (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
// I can't connect to the Login Service, just nlerror ();
|
||
|
string reason;
|
||
|
msgin.serial (reason);
|
||
|
nlerror (reason.c_str());
|
||
|
}
|
||
|
|
||
|
|
||
|
bool disconnectClient(uint32 userId)
|
||
|
{
|
||
|
map<uint32, TServiceId>::iterator it = UserIdSockAssociations.find (userId);
|
||
|
if (it == UserIdSockAssociations.end ())
|
||
|
{
|
||
|
nlinfo ("Login service ask to disconnect user %d, he is not connected here, so ignoring", userId);
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMessage msgout ("DC");
|
||
|
msgout.serial (userId);
|
||
|
CUnifiedNetwork::getInstance()->send (it->second, msgout);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void cbLSDisconnectClient (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
||
|
{
|
||
|
// the LS tells me that i have to disconnect a client
|
||
|
|
||
|
uint32 userid;
|
||
|
msgin.serial (userid);
|
||
|
|
||
|
disconnectClient(userid);
|
||
|
|
||
|
}
|
||
|
|
||
|
// connection to the LS, send the identification message
|
||
|
void cbLSConnection (const std::string &serviceName, TServiceId sid, void *arg)
|
||
|
{
|
||
|
sint32 shardId;
|
||
|
|
||
|
if (IService::getInstance()->haveArg('S'))
|
||
|
{
|
||
|
// use the command line param if set
|
||
|
shardId = atoi(IService::getInstance()->getArg('S').c_str());
|
||
|
}
|
||
|
else if (IService::getInstance()->ConfigFile.exists ("ShardId"))
|
||
|
{
|
||
|
// use the config file param if set
|
||
|
shardId = IService::getInstance()->ConfigFile.getVar ("ShardId").asInt();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
shardId = -1;
|
||
|
}
|
||
|
|
||
|
if (shardId == -1)
|
||
|
{
|
||
|
nlerror ("ShardId variable must be valid (>0)");
|
||
|
}
|
||
|
|
||
|
CMessage msgout ("WS_IDENT");
|
||
|
msgout.serial (shardId);
|
||
|
CUnifiedNetwork::getInstance()->send (sid, msgout);
|
||
|
|
||
|
nlinfo ("Connected to %s-%hu and sent identification with shardId '%d'", serviceName.c_str(), sid.get(), shardId);
|
||
|
|
||
|
// send state to LS
|
||
|
setShardOpenState((TShardOpenState)(ShardOpen.get()), false);
|
||
|
|
||
|
//
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
CMessage msgrpn("REPORT_NO_PATCH");
|
||
|
CUnifiedNetwork::getInstance()->send("LS", msgrpn);
|
||
|
}
|
||
|
|
||
|
bool reportPatching = false;
|
||
|
list<CFES>::iterator itfs;
|
||
|
for (itfs=FESList.begin(); itfs!=FESList.end(); ++itfs)
|
||
|
(*itfs).reportStateToLS(reportPatching);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Callback for detection of config file change about "ExpectedServices"
|
||
|
void cbUpdateExpectedServices( CConfigFile::CVar& var )
|
||
|
{
|
||
|
OnlineServices.setExpectedInstances( var );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ShardOpen update functions/callbacks etc.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* updateShardOpenFromFile()
|
||
|
* Update ShardOpen from a file.
|
||
|
* Read a line of text in the file, converts it to int (atoi), then casts into bool for ShardOpen.
|
||
|
*/
|
||
|
void updateShardOpenFromFile(const std::string& filename)
|
||
|
{
|
||
|
CIFile f;
|
||
|
|
||
|
if (!f.open(filename))
|
||
|
{
|
||
|
nlwarning("Failed to update ShardOpen from file '%s', couldn't open file", filename.c_str());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
char readBuffer[256];
|
||
|
f.getline(readBuffer, 256);
|
||
|
setShardOpenState((TShardOpenState)atoi(readBuffer));
|
||
|
|
||
|
nlinfo("Updated ShardOpen state to '%u' from file '%s'", ShardOpen.get(), filename.c_str());
|
||
|
}
|
||
|
catch (Exception& e)
|
||
|
{
|
||
|
nlwarning("Failed to update ShardOpen from file '%s', exception raised while getline() '%s'", filename.c_str(), e.what());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::string ShardOpenStateFileName;
|
||
|
|
||
|
/**
|
||
|
* cbShardOpen()
|
||
|
* Callback for ShardOpen
|
||
|
*/
|
||
|
void cbShardOpen(IVariable &var)
|
||
|
{
|
||
|
setShardOpenState((TShardOpenState)(ShardOpen.get()), false);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* cbShardOpenStateFile()
|
||
|
* Callback for ShardOpenStateFile
|
||
|
*/
|
||
|
void cbShardOpenStateFile(IVariable &var)
|
||
|
{
|
||
|
// remove previous file change callback
|
||
|
if (!ShardOpenStateFileName.empty())
|
||
|
{
|
||
|
CFile::removeFileChangeCallback(ShardOpenStateFileName);
|
||
|
nlinfo("Removed callback for ShardOpenStateFileName file '%s'", ShardOpenStateFileName.c_str());
|
||
|
}
|
||
|
|
||
|
ShardOpenStateFileName = var.toString();
|
||
|
|
||
|
if (!ShardOpenStateFileName.empty())
|
||
|
{
|
||
|
// set new callback for the file
|
||
|
CFile::addFileChangeCallback(ShardOpenStateFileName, updateShardOpenFromFile);
|
||
|
nlinfo("Set callback for ShardOpenStateFileName file '%s'", ShardOpenStateFileName.c_str());
|
||
|
|
||
|
// and update state from file...
|
||
|
updateShardOpenFromFile(ShardOpenStateFileName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* cbUsePatchMode()
|
||
|
* Callback for UsePatchMode
|
||
|
*/
|
||
|
void cbUsePatchMode(IVariable &var)
|
||
|
{
|
||
|
// if patch mode not set, set all fs in patching mode to accept clients now
|
||
|
if (!UsePatchMode.get())
|
||
|
{
|
||
|
nlinfo("UsePatchMode disabled, switch all patching servers to actual frontends");
|
||
|
|
||
|
list<CFES>::iterator it;
|
||
|
|
||
|
for (it=FESList.begin(); it!=FESList.end(); ++it)
|
||
|
{
|
||
|
if ((*it).State == PatchOnly)
|
||
|
{
|
||
|
(*it).setToAcceptClients();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Callback Array for message from LS
|
||
|
TUnifiedCallbackItem LSCallbackArray[] =
|
||
|
{
|
||
|
{ "CS", cbLSChooseShard },
|
||
|
{ "DC", cbLSDisconnectClient },
|
||
|
{ "FAILED", cbFailed },
|
||
|
};
|
||
|
|
||
|
class CWelcomeService : public IService
|
||
|
{
|
||
|
|
||
|
public:
|
||
|
|
||
|
/// Init the service, load the universal time.
|
||
|
void init ()
|
||
|
{
|
||
|
string FrontendServiceName = ConfigFile.getVar ("FrontendServiceName").asString();
|
||
|
|
||
|
try { FrontEndAddress = ConfigFile.getVar ("FrontEndAddress").asString(); } catch(Exception &) { }
|
||
|
|
||
|
nlinfo ("Waiting frontend services named '%s'", FrontendServiceName.c_str());
|
||
|
|
||
|
CUnifiedNetwork::getInstance()->setServiceUpCallback(FrontendServiceName, cbFESConnection, NULL);
|
||
|
CUnifiedNetwork::getInstance()->setServiceDownCallback(FrontendServiceName, cbFESDisconnection, NULL);
|
||
|
CUnifiedNetwork::getInstance()->setServiceUpCallback("*", cbServiceUp, NULL);
|
||
|
CUnifiedNetwork::getInstance()->setServiceDownCallback("*", cbServiceDown, NULL);
|
||
|
|
||
|
// add a connection to the LS
|
||
|
string LSAddr;
|
||
|
if (haveArg('T'))
|
||
|
{
|
||
|
// use the command line param if set
|
||
|
LSAddr = getArg('T');
|
||
|
}
|
||
|
else if (ConfigFile.exists ("LSHost"))
|
||
|
{
|
||
|
// use the config file param if set
|
||
|
LSAddr = ConfigFile.getVar("LSHost").asString();
|
||
|
}
|
||
|
|
||
|
if (haveArg('S'))
|
||
|
{
|
||
|
// use the command line param if set
|
||
|
uint shardId = atoi(IService::getInstance()->getArg('S').c_str());
|
||
|
|
||
|
nlinfo("Using shard id %u from command line '%s'", shardId, IService::getInstance()->getArg('S').c_str());
|
||
|
anticipateShardId(shardId);
|
||
|
}
|
||
|
else if (ConfigFile.exists ("ShardId"))
|
||
|
{
|
||
|
// use the config file param if set
|
||
|
uint shardId = IService::getInstance()->ConfigFile.getVar ("ShardId").asInt();
|
||
|
|
||
|
nlinfo("Using shard id %u from config file '%s'", shardId, IService::getInstance()->ConfigFile.getVar ("ShardId").asString().c_str());
|
||
|
anticipateShardId(shardId);
|
||
|
}
|
||
|
|
||
|
// the config file must have a valid address where the login service is
|
||
|
nlassert(!LSAddr.empty());
|
||
|
|
||
|
// add default port if not set by the config file
|
||
|
if (LSAddr.find (":") == string::npos)
|
||
|
LSAddr += ":49999";
|
||
|
|
||
|
AllowDispatchMsgToLS = true;
|
||
|
|
||
|
if (ConfigFile.getVarPtr("DontUseLSService") == NULL
|
||
|
|| !ConfigFile.getVar("DontUseLSService").asBool())
|
||
|
{
|
||
|
// We are using NeL Login Service
|
||
|
CUnifiedNetwork::getInstance()->addCallbackArray(LSCallbackArray, sizeof(LSCallbackArray)/sizeof(LSCallbackArray[0]));
|
||
|
if (!DontUseLS)
|
||
|
{
|
||
|
CUnifiedNetwork::getInstance()->setServiceUpCallback("LS", cbLSConnection, NULL);
|
||
|
CUnifiedNetwork::getInstance()->addService("LS", LSAddr);
|
||
|
}
|
||
|
}
|
||
|
// List of expected service instances
|
||
|
ConfigFile.setCallback( "ExpectedServices", cbUpdateExpectedServices );
|
||
|
cbUpdateExpectedServices( ConfigFile.getVar( "ExpectedServices" ) );
|
||
|
|
||
|
|
||
|
/*
|
||
|
* read config variable ShardOpenStateFile to update
|
||
|
*
|
||
|
*/
|
||
|
cbShardOpenStateFile(ShardOpenStateFile);
|
||
|
|
||
|
// // create a welcome service module (for SU comm)
|
||
|
// IModuleManager::getInstance().createModule("WelcomeService", "ws", "");
|
||
|
// // plug the module in the default gateway
|
||
|
// NLMISC::CCommandRegistry::getInstance().execute("ws.plug wg", InfoLog());
|
||
|
}
|
||
|
|
||
|
bool update ()
|
||
|
{
|
||
|
// update the service status
|
||
|
|
||
|
removeStatusTag("DEV_ONLY");
|
||
|
removeStatusTag("RESTRICTED");
|
||
|
removeStatusTag("Open");
|
||
|
|
||
|
if (ShardOpen == 0)
|
||
|
addStatusTag("DEV_ONLY");
|
||
|
else if (ShardOpen == 1)
|
||
|
addStatusTag("RESTRICTED");
|
||
|
else if (ShardOpen == 2)
|
||
|
addStatusTag("Open");
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
static const char* getCompleteServiceName(const IService* theService)
|
||
|
{
|
||
|
static std::string s;
|
||
|
s= "welcome_service";
|
||
|
|
||
|
if (theService->haveLongArg("wsname"))
|
||
|
{
|
||
|
s+= "_"+theService->getLongArg("wsname");
|
||
|
}
|
||
|
|
||
|
if (theService->haveLongArg("fullwsname"))
|
||
|
{
|
||
|
s= theService->getLongArg("fullwsname");
|
||
|
}
|
||
|
|
||
|
return s.c_str();
|
||
|
}
|
||
|
|
||
|
static const char* getShortServiceName(const IService* theService)
|
||
|
{
|
||
|
static std::string s;
|
||
|
s= "WS";
|
||
|
|
||
|
if (theService->haveLongArg("shortwsname"))
|
||
|
{
|
||
|
s= theService->getLongArg("shortwsname");
|
||
|
}
|
||
|
|
||
|
return s.c_str();
|
||
|
}
|
||
|
|
||
|
// Service instantiation
|
||
|
NLNET_SERVICE_MAIN( CWelcomeService, getShortServiceName(scn), getCompleteServiceName(scn), 0, FESCallbackArray, NELNS_CONFIG, NELNS_LOGS);
|
||
|
|
||
|
|
||
|
// welcome service module
|
||
|
//class CWelcomeServiceMod :
|
||
|
// public CEmptyModuleCommBehav<CEmptyModuleServiceBehav<CEmptySocketBehav<CModuleBase> > >,
|
||
|
// public WS::CWelcomeServiceSkel
|
||
|
//{
|
||
|
// void onProcessModuleMessage(IModuleProxy *sender, const CMessage &message)
|
||
|
// {
|
||
|
// if (CWelcomeServiceSkel::onDispatchMessage(sender, message))
|
||
|
// return;
|
||
|
//
|
||
|
// nlwarning("Unknown message '%s' received by '%s'",
|
||
|
// message.getName().c_str(),
|
||
|
// getModuleName().c_str());
|
||
|
// }
|
||
|
//
|
||
|
//
|
||
|
// ////// CWelcomeServiceSkel implementation
|
||
|
//
|
||
|
// // ask the welcome service to welcome a user
|
||
|
// virtual void welcomeUser(NLNET::IModuleProxy *sender, uint32 userId, const std::string &userName, const CLoginCookie &cookie, const std::string &priviledge, const std::string &exPriviledge, WS::TUserRole mode, uint32 instanceId)
|
||
|
// {
|
||
|
// string ret = lsChooseShard(userName,
|
||
|
// cookie,
|
||
|
// priviledge,
|
||
|
// exPriviledge,
|
||
|
// mode,
|
||
|
// instanceId);
|
||
|
//
|
||
|
// if (!ret.empty())
|
||
|
// {
|
||
|
// // TODO : correct this
|
||
|
// string fsAddr;
|
||
|
// CWelcomeServiceClientProxy wsc(sender);
|
||
|
// wsc.welcomeUserResult(this, userId, ret.empty(), fsAddr);
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// // ask the welcome service to disconnect a user
|
||
|
// virtual void disconnectUser(NLNET::IModuleProxy *sender, uint32 userId)
|
||
|
// {
|
||
|
// nlstop;
|
||
|
// }
|
||
|
//
|
||
|
//};
|
||
|
|
||
|
namespace WS
|
||
|
{
|
||
|
|
||
|
void CWelcomeServiceMod::onModuleUp(IModuleProxy *proxy)
|
||
|
{
|
||
|
if (proxy->getModuleClassName() == "RingSessionManager")
|
||
|
{
|
||
|
if (_RingSessionManager != NULL)
|
||
|
{
|
||
|
nlwarning("WelcomeServiceMod::onModuleUp : receiving module up for RingSessionManager '%s', but already have it as '%s', replacing it",
|
||
|
proxy->getModuleName().c_str(),
|
||
|
_RingSessionManager->getModuleName().c_str());
|
||
|
}
|
||
|
// store this module as the ring session manager
|
||
|
_RingSessionManager = proxy;
|
||
|
|
||
|
// say hello to our new friend (transmit fixed session id if set in config file)
|
||
|
nlinfo("Registering welcome service module into session manager '%s'", proxy->getModuleName().c_str());
|
||
|
uint32 sessionId = 0;
|
||
|
CConfigFile::CVar *varFixedSessionId = IService::getInstance()->ConfigFile.getVarPtr( "FixedSessionId" );
|
||
|
if ( varFixedSessionId )
|
||
|
sessionId = varFixedSessionId->asInt();
|
||
|
CWelcomeServiceClientProxy wscp(proxy);
|
||
|
wscp.registerWS(this, IService::getInstance()->getShardId(), sessionId, OnlineServices.getOnlineStatus());
|
||
|
|
||
|
// Send counts
|
||
|
uint32 totalNbOnlineUsers = 0, totalNbPendingUsers = 0;
|
||
|
for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
|
||
|
{
|
||
|
totalNbOnlineUsers += (*it).NbUser;
|
||
|
totalNbPendingUsers += (*it).NbPendingUsers;
|
||
|
}
|
||
|
CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers, totalNbPendingUsers);
|
||
|
}
|
||
|
else if (proxy->getModuleClassName() == "LoginService")
|
||
|
{
|
||
|
_LoginService = proxy;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CWelcomeServiceMod::onModuleDown(IModuleProxy *proxy)
|
||
|
{
|
||
|
if (_RingSessionManager == proxy)
|
||
|
{
|
||
|
// remove this module as the ring session manager
|
||
|
_RingSessionManager = NULL;
|
||
|
}
|
||
|
else if (_LoginService == proxy)
|
||
|
_LoginService = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CWelcomeServiceMod::welcomeUser(NLNET::IModuleProxy *sender, uint32 charId, const std::string &userName, const CLoginCookie &cookie, const std::string &priviledge, const std::string &exPriviledge, WS::TUserRole mode, uint32 instanceId)
|
||
|
{
|
||
|
nldebug( "ERLOG: welcomeUser(%u,%s,%s,%s,%s,%u,%u)", charId, userName.c_str(), cookie.toString().c_str(), priviledge.c_str(), exPriviledge.c_str(), (uint)mode.getValue(), instanceId );
|
||
|
string ret = lsChooseShard(userName,
|
||
|
cookie,
|
||
|
priviledge,
|
||
|
exPriviledge,
|
||
|
mode,
|
||
|
instanceId,
|
||
|
charId & 0xF);
|
||
|
|
||
|
uint32 userId = charId >> 4;
|
||
|
if (!ret.empty())
|
||
|
{
|
||
|
nldebug( "ERLOG: lsChooseShard returned an error => welcomeUserResult");
|
||
|
// TODO : correct this
|
||
|
string fsAddr;
|
||
|
CWelcomeServiceClientProxy wsc(sender);
|
||
|
wsc.welcomeUserResult(this, userId, false, fsAddr, ret);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nldebug( "ERLOG: lsChooseShard OK => adding to pending");
|
||
|
TPendingFEResponseInfo pfri;
|
||
|
pfri.WSMod = this;
|
||
|
pfri.UserId = userId;
|
||
|
pfri.WaiterModule = sender;
|
||
|
PendingFeResponse.insert(make_pair(cookie, pfri));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CWelcomeServiceMod::pendingUserLost(const NLNET::CLoginCookie &cookie)
|
||
|
{
|
||
|
if (!_LoginService)
|
||
|
return;
|
||
|
|
||
|
CLoginServiceProxy ls(_LoginService);
|
||
|
|
||
|
ls.pendingUserLost(this, cookie);
|
||
|
}
|
||
|
|
||
|
|
||
|
// register the module
|
||
|
NLNET_REGISTER_MODULE_FACTORY(CWelcomeServiceMod, "WelcomeService");
|
||
|
|
||
|
} // namespace WS
|
||
|
|
||
|
|
||
|
//
|
||
|
// Variables
|
||
|
//
|
||
|
|
||
|
NLMISC_DYNVARIABLE(uint32, OnlineUsersNumber, "number of connected users on this shard")
|
||
|
{
|
||
|
// we can only read the value
|
||
|
if (get)
|
||
|
{
|
||
|
uint32 nbusers = 0;
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end (); it++)
|
||
|
{
|
||
|
nbusers += (*it).NbUser;
|
||
|
}
|
||
|
*pointer = nbusers;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Commands
|
||
|
//
|
||
|
|
||
|
|
||
|
NLMISC_COMMAND (frontends, "displays the list of all registered front ends", "")
|
||
|
{
|
||
|
if(args.size() != 0) return false;
|
||
|
|
||
|
log.displayNL ("Display the %d registered front end :", FESList.size());
|
||
|
for (list<CFES>::iterator it = FESList.begin(); it != FESList.end (); it++)
|
||
|
{
|
||
|
// log.displayNL ("> FE %u: nb estimated users: %u nb users: %u, nb pending users : %u",
|
||
|
log.displayNL ("> FE %u: nb users: %u, nb pending users : %u",
|
||
|
it->SId.get(),
|
||
|
it->NbUser,
|
||
|
it->NbPendingUsers);
|
||
|
}
|
||
|
log.displayNL ("End ot the list");
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
NLMISC_COMMAND (users, "displays the list of all registered users", "")
|
||
|
{
|
||
|
if(args.size() != 0) return false;
|
||
|
|
||
|
log.displayNL ("Display the %d registered users :", UserIdSockAssociations.size());
|
||
|
for (map<uint32, TServiceId>::iterator it = UserIdSockAssociations.begin(); it != UserIdSockAssociations.end (); it++)
|
||
|
{
|
||
|
log.displayNL ("> %u SId=%u", (*it).first, (*it).second.get());
|
||
|
}
|
||
|
log.displayNL ("End ot the list");
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
NLMISC_COMMAND( displayOnlineServices, "Display the online service instances", "" )
|
||
|
{
|
||
|
OnlineServices.display( log );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
NLMISC_VARIABLE( bool, OnlineStatus, "Main online status of the shard" );
|
||
|
|
||
|
|