khanat-opennel-code/code/nelns/welcome_service/welcome_service.cpp
2013-06-15 07:08:38 -07:00

1583 lines
41 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", "Don't 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" );