1 // NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #endif // HAVE_CONFIG_H
22 #define NELNS_CONFIG ""
23 #endif // NELNS_CONFIG
29 #include "nel/misc/types_nl.h"
37 #include "nel/misc/debug.h"
38 #include "nel/misc/config_file.h"
39 #include "nel/misc/displayer.h"
40 #include "nel/misc/command.h"
41 #include "nel/misc/variable.h"
42 #include "nel/misc/log.h"
43 #include "nel/misc/file.h"
44 #include "nel/misc/path.h"
46 #include "nel/net/service.h"
47 #include "nel/net/unified_network.h"
48 #include "nel/net/login_cookie.h"
50 #include "welcome_service.h"
53 using namespace NLMISC
;
54 using namespace NLNET
;
58 CVariable
<sint
> PlayerLimit(
59 "ws","PlayerLimit", "Rough max number of players accepted on this shard (-1 for Unlimited)",
63 // Forward declaration of callback cbShardOpen (see ShardOpen variable)
64 void cbShardOpen(IVariable
&var
);
66 // Forward declaration of callback cbShardOpenStateFile (see ShardOpenStateFile variable)
67 void cbShardOpenStateFile(IVariable
&var
);
69 // Forward declaration of callback cbUsePatchMode
70 void cbUsePatchMode(IVariable
&var
);
72 // Types of open state
76 OpenOnlyForAllowed
= 1,
80 static bool AllowDispatchMsgToLS
= false;
84 * true if shard is open to public
85 * 0 means closed for all but :DEV:
86 * 1 means open only for groups in config file (see OpenGroups variable) and :DEV:
87 * 2 means open for all
89 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
);
93 * true if shard is open to public
95 CVariable
<string
> ShardOpenStateFile("ws", "ShardOpenStateFile", "Name of the file that contains ShardOpen state", "", 0, true, cbShardOpenStateFile
);
100 CVariable
<string
> OpenGroups("ws", "OpenGroups", "list of groups allowed at ShardOpen Level 1", "", 0, true);
103 * OpenFrontEndThreshold
104 * The FS balance algorithm works like this:
105 * - select the least loaded frontend
106 * - if this frontend has more than the OpenFrontEndThreshold
107 * - try to open a new frontend
108 * - reselect least loaded frontend
110 CVariable
<uint
> OpenFrontEndThreshold("ws", "OpenFrontEndThreshold", "Limit number of players on all FS to decide to open a new FS", 800, 0, true );
116 CVariable
<bool> UsePatchMode("ws", "UsePatchMode", "Use Frontends as Patch servers (at FS startup)", true, 0, true, cbUsePatchMode
);
121 CVariable
<bool> DontUseLS("ws", "DontUseLS", "Don't use the login service", false, 0, true);
124 // Shortcut to the module instance
125 //CWelcomeServiceMod *CWelcomeServiceMod::_Instance = NULL;
129 * Using expected services and current running service instances, this class
130 * reports a main "online status".
132 class COnlineServices
136 /// Set expected instances. Ex: { "TICKS", "FS", "FS", "FS" }
137 void setExpectedInstances( CConfigFile::CVar
& var
)
139 // Reset "expected" counters (but don't clear the map, keep the running instances)
140 CInstances::iterator ici
;
141 for ( ici
=_Instances
.begin(); ici
!=_Instances
.end(); ++ici
)
143 (*ici
).second
.Expected
= 0;
145 // Rebuild "expected" counters
146 for ( uint i
=0; i
!=var
.size(); ++i
)
148 ++_Instances
[var
.asString(i
)].Expected
;
152 /// Add a service instance
153 void addInstance( const std::string
& serviceName
)
155 ++_Instances
[serviceName
].Running
;
158 /// Remove a service instance
159 void removeInstance( const std::string
& serviceName
)
161 CInstances::iterator ici
= _Instances
.find( serviceName
);
162 if ( ici
!= _Instances
.end() )
164 --(*ici
).second
.Running
;
166 // Remove from the map only if not part of the expected list
167 if ( ((*ici
).second
.Expected
== 0) && ((*ici
).second
.Running
== 0) )
169 _Instances
.erase( ici
);
174 nlwarning( "Can't remove instance of %s", serviceName
.c_str() );
178 /// Check if all expected instances are online
179 bool getOnlineStatus() const
181 CInstances::const_iterator ici
;
182 for ( ici
=_Instances
.begin(); ici
!=_Instances
.end(); ++ici
)
184 if ( ! ici
->second
.isOnlineAsExpected() )
191 void display( NLMISC::CLog
& log
= *NLMISC::DebugLog
)
193 CInstances::const_iterator ici
;
194 for ( ici
=_Instances
.begin(); ici
!=_Instances
.end(); ++ici
)
196 log
.displayNL( "%s: %s (%u expected, %u running)",
197 (*ici
).first
.c_str(),
198 (*ici
).second
.Expected
? ((*ici
).second
.isOnlineAsExpected() ? "ONLINE" : "MISSING") : "OPTIONAL",
199 (*ici
).second
.Expected
, (*ici
).second
.Running
);
205 struct TInstanceCounters
207 TInstanceCounters() : Expected(0), Running(0) {}
209 // If not expected, count as online as well
210 bool isOnlineAsExpected() const { return Running
>= Expected
; }
216 typedef std::map
< std::string
, TInstanceCounters
> CInstances
;
218 CInstances _Instances
;
222 COnlineServices OnlineServices
;
225 /// Main online status
228 /// Send changes of status to the LS
229 void reportOnlineStatus( bool newStatus
)
231 if ( newStatus
!= OnlineStatus
&& AllowDispatchMsgToLS
)
235 CMessage
msgout( "OL_ST" );
236 msgout
.serial( newStatus
);
237 CUnifiedNetwork::getInstance()->send( "LS", msgout
);
240 if (CWelcomeServiceMod::isInitialized())
242 // send a status report to welcome service client
243 CWelcomeServiceMod::getInstance()->reportWSOpenState(newStatus
);
246 OnlineStatus
= newStatus
;
252 /// Set the version of the shard. you have to increase it each time the client-server protocol changes.
253 /// You have to increment the client too (the server and client version must be the same to run correctly)
254 static const uint32 ServerVersion
= 1;
256 /// Contains the correspondance between userid and the FES connection where the userid is connected.
257 map
<uint32
, TServiceId
> UserIdSockAssociations
;
260 string FrontEndAddress
;
272 CFES (TServiceId sid
) : SId(sid
), NbPendingUsers(0), NbUser(0), State(PatchOnly
) { }
274 TServiceId SId
; // Connection to the front end
275 uint32 NbPendingUsers
; // Number of not yet connected users (but rooted to this frontend)
276 uint32 NbUser
; // Number of user currently connected on this front end
278 TFESState State
; // State of frontend (patching/accepting clients)
279 std::string PatchAddress
; // Address of frontend patching server
281 uint32
getUsersCountHeuristic() const
283 return NbUser
+ NbPendingUsers
;
286 void setToAcceptClients()
288 if (State
== AcceptClientOnly
)
291 // tell FS to accept client
292 State
= AcceptClientOnly
;
293 CMessage
msgOpenFES("FS_ACCEPT");
294 CUnifiedNetwork::getInstance()->send(SId
, msgOpenFES
);
296 // report state to LS
298 reportStateToLS(dummy
, true);
301 void reportStateToLS(bool& reportPatching
, bool alive
= true)
305 bool patching
= (State
== PatchOnly
);
306 if (alive
&& patching
)
307 reportPatching
= true;
309 if ( AllowDispatchMsgToLS
)
313 CMessage
msgout("REPORT_FS_STATE");
315 msgout
.serial(alive
);
316 msgout
.serial(patching
);
317 msgout
.serial(PatchAddress
);
318 CUnifiedNetwork::getInstance()->send("LS", msgout
);
327 * Find the best front end service for a new connecting user (return NULL if there is no suitable FES).
328 * Additionally, calculate totalNbUsers.
330 CFES
*findBestFES ( uint
& totalNbUsers
)
336 for (list
<CFES
>::iterator it
=FESList
.begin(); it
!=FESList
.end(); ++it
)
339 if (fes
.State
== AcceptClientOnly
)
341 if (best
== NULL
|| best
->getUsersCountHeuristic() > fes
.getUsersCountHeuristic())
344 totalNbUsers
+= fes
.NbUser
;
353 * Select a frontend in patch mode to open
354 * Returns true if a new FES was open, false if no FES could be open
358 for (list
<CFES
>::iterator it
=FESList
.begin(); it
!=FESList
.end(); ++it
)
360 if ((*it
).State
== PatchOnly
)
362 nlinfo("openNewFES: ask the FS %d to accept clients", it
->SId
.get());
364 // switch FES to AcceptClientOnly
365 (*it
).setToAcceptClients();
377 nlinfo ("There's %d FES in the list:", FESList
.size());
378 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
380 nlinfo(" > %u NbUser:%d NbPendingUser:%d", it
->SId
.get(), it
->NbUser
, it
->NbPendingUsers
);
382 nlinfo ("End of the list");
390 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
391 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
392 //////////////////// CONNECTION TO THE FRONT END SERVICE ///////////////////////////////////////////////////////
393 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
394 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
396 void cbFESShardChooseShard (CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
398 // the WS answer a user authorize
404 // S09: receive "SCS" message from FES and send the "SCS" message to the LS
407 CMessage
msgout ("SCS");
409 msgin
.serial (reason
);
410 msgout
.serial (reason
);
412 msgin
.serial (cookie
);
413 msgout
.serial (cookie
);
419 // if we set the FontEndAddress in the welcome_service.cfg we use this address
420 if (FrontEndAddress
.empty())
422 msgout
.serial (addr
);
426 msgout
.serial (FrontEndAddress
);
429 uint32 nbPendingUser
;
430 msgin
.serial(nbPendingUser
);
432 // update the pending user count for this shard
433 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
437 it
->NbPendingUsers
= nbPendingUser
;
443 // OBSOLETE: LS doesn't read patching URLs
444 // build patch server list
445 std::string PatchURLS;
446 for (list<CFES>::iterator it=FESList.begin(); it!=FESList.end(); ++it)
448 if ((*it).State == PatchOnly && !(*it).PatchAddress.empty())
450 if (!PatchURLS.empty())
452 PatchURLS += (*it).PatchAddress;
457 msgout.serial(PatchURLS);
461 if (PendingFeResponse
.find(cookie
) != PendingFeResponse
.end())
463 nldebug( "ERLOG: SCS recvd from %s-%hu => sending %s to SU", serviceName
.c_str(), sid
.get(), cookie
.toString().c_str());
465 // this response is not waited by LS
466 TPendingFEResponseInfo
&pfri
= PendingFeResponse
.find(cookie
)->second
;
468 pfri
.WSMod
->frontendResponse(pfri
.WaiterModule
, pfri
.UserId
, reason
, cookie
, addr
);
469 // cleanup pending record
470 PendingFeResponse
.erase(cookie
);
474 nldebug( "ERLOG: SCS recvd from %s-%hu, but pending %s not found", serviceName
.c_str(), sid
.get(), cookie
.toString().c_str());
476 // return the result to the LS
479 CUnifiedNetwork::getInstance()->send ("LS", msgout
);
485 // This function is call when a FES accepted a new client or lost a connection to a client
486 void cbFESClientConnected (CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
489 // S15: receive "CC" message from FES and send "CC" message to the "LS"
492 CMessage
msgout ("CC");
495 msgin
.serial (userid
);
496 msgout
.serial (userid
);
504 CUnifiedNetwork::getInstance()->send ("LS", msgout
);
507 // add or remove the user number really connected on this shard
508 uint32 totalNbOnlineUsers
= 0, totalNbPendingUsers
= 0;
509 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
517 // the client connected, it's no longer pending
518 if ((*it
).NbPendingUsers
> 0)
519 (*it
).NbPendingUsers
--;
523 if ( (*it
).NbUser
!= 0 )
527 totalNbOnlineUsers
+= (*it
).NbUser
;
528 totalNbPendingUsers
+= (*it
).NbPendingUsers
;
531 if (CWelcomeServiceMod::isInitialized())
532 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers
, totalNbPendingUsers
);
536 // we know that this user is on this FES
537 UserIdSockAssociations
.insert (make_pair (userid
, sid
));
542 UserIdSockAssociations
.erase (userid
);
547 // This function is called when a FES rejected a client' cookie
548 void cbFESRemovedPendingCookie(CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
551 msgin
.serial(cookie
);
552 nldebug( "ERLOG: RPC recvd from %s-%hu => %s removed", serviceName
.c_str(), sid
.get(), cookie
.toString().c_str(), cookie
.toString().c_str());
555 // client' cookie rejected, no longer pending
556 uint32 totalNbOnlineUsers
= 0, totalNbPendingUsers
= 0;
557 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
559 if ((*it
).SId
== sid
)
561 if ((*it
).NbPendingUsers
> 0)
562 --(*it
).NbPendingUsers
;
564 totalNbOnlineUsers
+= (*it
).NbUser
;
565 totalNbPendingUsers
+= (*it
).NbPendingUsers
;
568 if (CWelcomeServiceMod::isInitialized())
570 CWelcomeServiceMod::getInstance()->pendingUserLost(cookie
);
571 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers
, totalNbPendingUsers
);
575 // This function is called by FES to setup its PatchAddress
576 void cbFESPatchAddress(CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
579 msgin
.serial(address
);
582 msgin
.serial(acceptClients
);
584 nldebug("Received patch server address '%s' from service %s %d", address
.c_str(), serviceName
.c_str(), sid
.get());
586 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
588 if ((*it
).SId
== sid
)
590 nldebug("Affected patch server address '%s' to frontend %s %d", address
.c_str(), serviceName
.c_str(), sid
.get());
592 if (!UsePatchMode
.get() && !acceptClients
)
594 // not in patch mode, force fs to accept clients
595 acceptClients
= true;
596 (*it
).setToAcceptClients();
599 (*it
).PatchAddress
= address
;
600 (*it
).State
= (acceptClients
? AcceptClientOnly
: PatchOnly
);
602 nldebug("Frontend %s %d reported to accept client, patching unavailable for that server", address
.c_str(), serviceName
.c_str(), sid
.get());
604 nldebug("Frontend %s %d reported to be in patching mode", address
.c_str(), serviceName
.c_str(), sid
.get());
607 (*it
).reportStateToLS(dummy
);
613 // This function is called by FES to setup the right number of players (if FES was already present before WS launching)
614 void cbFESNbPlayers(CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
616 // *********** WARNING *******************
617 // This version of the callback is deprecated, the system
618 // now use cbFESNbPlayers2 that report the pending user count
619 // as well as the number of connected players.
620 // It is kept for backward compatibility only.
621 // ***************************************
624 msgin
.serial(nbPlayers
);
626 uint32 totalNbOnlineUsers
= 0, totalNbPendingUsers
= 0;
627 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
629 if ((*it
).SId
== sid
)
631 nldebug("Frontend '%d' reported %d online users", sid
.get(), nbPlayers
);
632 (*it
).NbUser
= nbPlayers
;
633 if (nbPlayers
!= 0 && (*it
).State
== PatchOnly
)
635 nlwarning("Frontend %d is in state PatchOnly, yet reports to have online %d players, state AcceptClientOnly is forced (FS_ACCEPT message sent)");
636 (*it
).setToAcceptClients();
639 totalNbOnlineUsers
+= (*it
).NbUser
;
640 totalNbPendingUsers
+= (*it
).NbPendingUsers
;
643 if (CWelcomeServiceMod::isInitialized())
644 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers
, totalNbPendingUsers
);
648 // This function is called by FES to setup the right number of players (if FES was already present before WS launching)
649 void cbFESNbPlayers2(CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
652 uint32 nbPendingPlayers
;
653 msgin
.serial(nbPlayers
);
654 msgin
.serial(nbPendingPlayers
);
656 uint32 totalNbOnlineUsers
= 0, totalNbPendingUsers
= 0;
657 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
662 nldebug("Frontend '%d' reported %d online users", sid
.get(), nbPlayers
);
663 fes
.NbUser
= nbPlayers
;
664 fes
.NbPendingUsers
= nbPendingPlayers
;
665 if (nbPlayers
!= 0 && fes
.State
== PatchOnly
)
667 nlwarning("Frontend %d is in state PatchOnly, yet reports to have online %d players, state AcceptClientOnly is forced (FS_ACCEPT message sent)");
668 (*it
).setToAcceptClients();
671 totalNbOnlineUsers
+= fes
.NbUser
;
672 totalNbPendingUsers
+= fes
.NbPendingUsers
;
675 if (CWelcomeServiceMod::isInitialized())
676 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers
, totalNbPendingUsers
);
680 * Set Shard open state
682 void setShardOpenState(TShardOpenState state
, bool writeInVar
= true)
687 if ( AllowDispatchMsgToLS
)
691 // send to LS current shard state
692 CMessage
msgout ("SET_SHARD_OPEN");
693 uint8 shardOpenState
= (uint8
)state
;
695 msgout
.serial (shardOpenState
);
696 CUnifiedNetwork::getInstance()->send ("LS", msgout
);
703 * Set Shard Open State
704 * uint8 Open State (0 closed for all, 1 open for groups in cfg, 2 open for all)
706 void cbSetShardOpen(CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
708 uint8 shardOpenState
;
709 msgin
.serial (shardOpenState
);
711 if (shardOpenState
> OpenForAll
)
713 shardOpenState
= OpenForAll
;
716 setShardOpenState((TShardOpenState
)shardOpenState
);
719 // forward declaration to callback
720 void cbShardOpenStateFile(IVariable
&var
);
723 * Restore Shard Open state from config file or from file if found
725 void cbRestoreShardOpen(CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
727 // first restore state from config file
728 CConfigFile::CVar
* var
= IService::getInstance()->ConfigFile
.getVarPtr("ShardOpen");
731 setShardOpenState((TShardOpenState
)var
->asInt());
734 // then restore state from state file, if it exists
735 cbShardOpenStateFile(ShardOpenStateFile
);
742 // a new front end connecting to me, add it
743 void cbFESConnection (const std::string
&serviceName
, TServiceId sid
, void *arg
)
745 FESList
.push_back (CFES ((TServiceId
)sid
));
746 nldebug("new FES connection: sid %u", sid
.get());
750 FESList
.back().reportStateToLS(dummy
);
752 if (!UsePatchMode
.get())
754 FESList
.back().setToAcceptClients();
759 // a front end closes the connection, deconnect him
760 void cbFESDisconnection (const std::string
&serviceName
, TServiceId sid
, void *arg
)
762 nldebug("new FES disconnection: sid %u", sid
.get());
764 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
766 if ((*it
).SId
== sid
)
768 // send a message to the LS to say that all players from this FES are offline
769 map
<uint32
, TServiceId
>::iterator itc
= UserIdSockAssociations
.begin();
770 map
<uint32
, TServiceId
>::iterator nitc
= itc
;
771 while (itc
!= UserIdSockAssociations
.end())
774 if ((*itc
).second
== sid
)
776 // bye bye little player
777 uint32 userid
= (*itc
).first
;
778 nlinfo ("Due to a frontend crash, removed the player %d", userid
);
781 CMessage
msgout ("CC");
782 msgout
.serial (userid
);
785 CUnifiedNetwork::getInstance()->send ("LS", msgout
);
787 UserIdSockAssociations
.erase (itc
);
793 (*it
).reportStateToLS(dummy
, false);
802 // Update the welcome service client with the new count of connection
804 uint32 totalNbOnlineUsers
=0, totalNbPendingUsers
= 0;
805 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end(); it
++)
807 const CFES
&fes
= *it
;
808 totalNbOnlineUsers
+= fes
.NbUser
;
809 totalNbPendingUsers
+= fes
.NbPendingUsers
;
812 if (CWelcomeServiceMod::isInitialized())
813 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers
, totalNbPendingUsers
);
820 void cbServiceUp (const std::string
&serviceName
, TServiceId sid
, void *arg
)
822 OnlineServices
.addInstance( serviceName
);
823 bool online
= OnlineServices
.getOnlineStatus();
824 reportOnlineStatus( online
);
826 // send shard id to service
828 if (IService::getInstance()->haveArg('S'))
830 // use the command line param if set
831 shardId
= atoi(IService::getInstance()->getArg('S').c_str());
833 else if (IService::getInstance()->ConfigFile
.exists ("ShardId"))
835 // use the config file param if set
836 shardId
= IService::getInstance()->ConfigFile
.getVar ("ShardId").asInt();
845 nlerror ("ShardId variable must be valid (>0)");
848 CMessage
msgout("R_SH_ID");
849 msgout
.serial(shardId
);
850 CUnifiedNetwork::getInstance()->send (sid
, msgout
);
855 void cbServiceDown (const std::string
&serviceName
, TServiceId sid
, void *arg
)
857 OnlineServices
.removeInstance( serviceName
);
858 bool online
= OnlineServices
.getOnlineStatus();
859 reportOnlineStatus( online
);
863 // Callback Array for message from FES
864 TUnifiedCallbackItem FESCallbackArray
[] =
866 { "SCS", cbFESShardChooseShard
},
867 { "CC", cbFESClientConnected
},
868 { "RPC", cbFESRemovedPendingCookie
},
869 { "FEPA", cbFESPatchAddress
},
870 { "NBPLAYERS", cbFESNbPlayers
},
871 { "NBPLAYERS2", cbFESNbPlayers2
},
873 { "SET_SHARD_OPEN", cbSetShardOpen
},
874 { "RESTORE_SHARD_OPEN", cbRestoreShardOpen
},
878 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
879 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
880 //////////////////// CONNECTION TO THE LOGIN SERVICE ///////////////////////////////////////////////////////////
881 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
882 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
883 void cbLSChooseShard (CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
885 // the LS warns me that a new client want to come in my shard
887 nldebug( "ERLOG: CS recvd from %s-%hu", serviceName
.c_str(), sid
.get());
890 // S07: receive the "CS" message from LS and send the "CS" message to the selected FES
894 msgin
.serial (cookie
);
895 string userName
, userPriv
, userExtended
;
896 msgin
.serial (userName
);
900 msgin
.serial (userPriv
);
904 nlwarning ("LS didn't give me the user privilege for user '%s', set to empty", userName
.c_str());
909 msgin
.serial (userExtended
);
913 nlwarning ("LS didn't give me the extended data for user '%s', set to empty", userName
.c_str());
917 string ret
= lsChooseShard(userName
, cookie
, userPriv
, userExtended
, WS::TUserRole::ur_player
, 0xffffffff, ~0);
921 // send back an error message to LS
922 CMessage
msgout ("SCS");
924 msgout
.serial (cookie
);
925 CUnifiedNetwork::getInstance()->send(sid
, msgout
);
929 //void cbLSChooseShard (CMessage &msgin, const std::string &serviceName, uint16 sid)
930 std::string
lsChooseShard (const std::string
&userName
,
931 const CLoginCookie
&cookie
,
932 const std::string
&userPriv
,
933 const std::string
&userExtended
,
934 WS::TUserRole userRole
,
938 // the LS warns me that a new client want to come in my shard
941 // S07: receive the "CS" message from LS and send the "CS" message to the selected FES
946 CFES *best = findBestFES( totalNbUsers );
949 // answer the LS that we can't accept the user
950 CMessage msgout ("SCS");
951 string reason = "No front-end server available";
952 msgout.serial (reason);
953 msgout.serial (cookie);
954 CUnifiedNetwork::getInstance()->send(sid, msgout);
960 CFES
* best
= findBestFES( totalNbUsers
);
962 // could not find a good FES or best FES has more players than balance limit
963 if (best
== NULL
|| best
->getUsersCountHeuristic() >= OpenFrontEndThreshold
)
965 // open a new frontend
968 // reselect best FES (will return newly open FES, or previous if no more FES available)
969 best
= findBestFES(totalNbUsers
);
971 // check there is a FES available
974 // answer the LS that we can't accept the user
975 return "No front-end server available";
980 bool authorizeUser
= false;
981 bool forceAuthorize
= false;
983 if (userPriv
== ":DEV:")
985 // devs have all privileges
986 authorizeUser
= true;
987 forceAuthorize
= true;
989 else if (ShardOpen
!= ClosedForAll
)
991 const std::string
& allowedGroups
= OpenGroups
;
992 bool userInOpenGroups
= (!userPriv
.empty() && !allowedGroups
.empty() && allowedGroups
.find(userPriv
) != std::string::npos
);
994 // open for all or user is privileged
995 authorizeUser
= (ShardOpen
== OpenForAll
|| userInOpenGroups
);
996 // let authorized users to force access even if limit is reached
997 forceAuthorize
= userInOpenGroups
;
1000 bool shardLimitReached
= ( (PlayerLimit
.get() != -1) && (totalNbUsers
>= (uint
)PlayerLimit
.get()) );
1002 if (!forceAuthorize
&& (!authorizeUser
|| shardLimitReached
))
1004 // answer the LS that we can't accept the user
1005 CMessage
msgout ("SCS");
1007 if (shardLimitReached
)
1008 return "The shard is currently full, please try again in 5 minutes.";
1010 return "The shard is closed.";
1014 CMessage
msgout ("CS");
1015 msgout
.serial (const_cast<CLoginCookie
&>(cookie
));
1016 msgout
.serial (const_cast<string
&>(userName
), const_cast<string
&>(userPriv
), const_cast<string
&>(userExtended
));
1017 msgout
.serial (instanceId
);
1018 msgout
.serial (charSlot
);
1020 CUnifiedNetwork::getInstance()->send (best
->SId
, msgout
);
1021 best
->NbPendingUsers
++;
1024 uint32 totalNbOnlineUsers
= 0, totalNbPendingUsers
= 0;
1025 for (list
<CFES
>::iterator it
=FESList
.begin(); it
!=FESList
.end(); ++it
)
1027 totalNbOnlineUsers
+= (*it
).NbUser
;
1028 totalNbPendingUsers
+= (*it
).NbPendingUsers
;
1030 if (CWelcomeServiceMod::isInitialized())
1031 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers
, totalNbPendingUsers
);
1036 void cbFailed (CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
1038 // I can't connect to the Login Service, just nlerror ();
1040 msgin
.serial (reason
);
1041 nlerror (reason
.c_str());
1045 bool disconnectClient(uint32 userId
)
1047 map
<uint32
, TServiceId
>::iterator it
= UserIdSockAssociations
.find (userId
);
1048 if (it
== UserIdSockAssociations
.end ())
1050 nlinfo ("Login service ask to disconnect user %d, he is not connected here, so ignoring", userId
);
1055 CMessage
msgout ("DC");
1056 msgout
.serial (userId
);
1057 CUnifiedNetwork::getInstance()->send (it
->second
, msgout
);
1063 void cbLSDisconnectClient (CMessage
&msgin
, const std::string
&serviceName
, TServiceId sid
)
1065 // the LS tells me that i have to disconnect a client
1068 msgin
.serial (userid
);
1070 disconnectClient(userid
);
1074 // connection to the LS, send the identification message
1075 void cbLSConnection (const std::string
&serviceName
, TServiceId sid
, void *arg
)
1079 if (IService::getInstance()->haveArg('S'))
1081 // use the command line param if set
1082 shardId
= atoi(IService::getInstance()->getArg('S').c_str());
1084 else if (IService::getInstance()->ConfigFile
.exists ("ShardId"))
1086 // use the config file param if set
1087 shardId
= IService::getInstance()->ConfigFile
.getVar ("ShardId").asInt();
1096 nlerror ("ShardId variable must be valid (>0)");
1099 CMessage
msgout ("WS_IDENT");
1100 msgout
.serial (shardId
);
1101 CUnifiedNetwork::getInstance()->send (sid
, msgout
);
1103 nlinfo ("Connected to %s-%hu and sent identification with shardId '%d'", serviceName
.c_str(), sid
.get(), shardId
);
1106 setShardOpenState((TShardOpenState
)(ShardOpen
.get()), false);
1111 CMessage
msgrpn("REPORT_NO_PATCH");
1112 CUnifiedNetwork::getInstance()->send("LS", msgrpn
);
1115 bool reportPatching
= false;
1116 list
<CFES
>::iterator itfs
;
1117 for (itfs
=FESList
.begin(); itfs
!=FESList
.end(); ++itfs
)
1118 (*itfs
).reportStateToLS(reportPatching
);
1122 // Callback for detection of config file change about "ExpectedServices"
1123 void cbUpdateExpectedServices( CConfigFile::CVar
& var
)
1125 OnlineServices
.setExpectedInstances( var
);
1130 * ShardOpen update functions/callbacks etc.
1134 * updateShardOpenFromFile()
1135 * Update ShardOpen from a file.
1136 * Read a line of text in the file, converts it to int (atoi), then casts into bool for ShardOpen.
1138 void updateShardOpenFromFile(const std::string
& filename
)
1142 if (!f
.open(filename
))
1144 nlwarning("Failed to update ShardOpen from file '%s', couldn't open file", filename
.c_str());
1150 char readBuffer
[256];
1151 f
.getline(readBuffer
, 256);
1152 setShardOpenState((TShardOpenState
)atoi(readBuffer
));
1154 nlinfo("Updated ShardOpen state to '%u' from file '%s'", ShardOpen
.get(), filename
.c_str());
1156 catch (Exception
& e
)
1158 nlwarning("Failed to update ShardOpen from file '%s', exception raised while getline() '%s'", filename
.c_str(), e
.what());
1162 std::string ShardOpenStateFileName
;
1166 * Callback for ShardOpen
1168 void cbShardOpen(IVariable
&var
)
1170 setShardOpenState((TShardOpenState
)(ShardOpen
.get()), false);
1175 * cbShardOpenStateFile()
1176 * Callback for ShardOpenStateFile
1178 void cbShardOpenStateFile(IVariable
&var
)
1180 // remove previous file change callback
1181 if (!ShardOpenStateFileName
.empty())
1183 CFile::removeFileChangeCallback(ShardOpenStateFileName
);
1184 nlinfo("Removed callback for ShardOpenStateFileName file '%s'", ShardOpenStateFileName
.c_str());
1187 ShardOpenStateFileName
= var
.toString();
1189 if (!ShardOpenStateFileName
.empty())
1191 // set new callback for the file
1192 CFile::addFileChangeCallback(ShardOpenStateFileName
, updateShardOpenFromFile
);
1193 nlinfo("Set callback for ShardOpenStateFileName file '%s'", ShardOpenStateFileName
.c_str());
1195 // and update state from file...
1196 updateShardOpenFromFile(ShardOpenStateFileName
);
1202 * Callback for UsePatchMode
1204 void cbUsePatchMode(IVariable
&var
)
1206 // if patch mode not set, set all fs in patching mode to accept clients now
1207 if (!UsePatchMode
.get())
1209 nlinfo("UsePatchMode disabled, switch all patching servers to actual frontends");
1211 list
<CFES
>::iterator it
;
1213 for (it
=FESList
.begin(); it
!=FESList
.end(); ++it
)
1215 if ((*it
).State
== PatchOnly
)
1217 (*it
).setToAcceptClients();
1224 // Callback Array for message from LS
1225 TUnifiedCallbackItem LSCallbackArray
[] =
1227 { "CS", cbLSChooseShard
},
1228 { "DC", cbLSDisconnectClient
},
1229 { "FAILED", cbFailed
},
1232 class CWelcomeService
: public IService
1237 /// Init the service, load the universal time.
1240 string FrontendServiceName
= ConfigFile
.getVar ("FrontendServiceName").asString();
1242 try { FrontEndAddress
= ConfigFile
.getVar ("FrontEndAddress").asString(); } catch(Exception
&) { }
1244 nlinfo ("Waiting frontend services named '%s'", FrontendServiceName
.c_str());
1246 CUnifiedNetwork::getInstance()->setServiceUpCallback(FrontendServiceName
, cbFESConnection
, NULL
);
1247 CUnifiedNetwork::getInstance()->setServiceDownCallback(FrontendServiceName
, cbFESDisconnection
, NULL
);
1248 CUnifiedNetwork::getInstance()->setServiceUpCallback("*", cbServiceUp
, NULL
);
1249 CUnifiedNetwork::getInstance()->setServiceDownCallback("*", cbServiceDown
, NULL
);
1251 // add a connection to the LS
1255 // use the command line param if set
1256 LSAddr
= getArg('T');
1258 else if (ConfigFile
.exists ("LSHost"))
1260 // use the config file param if set
1261 LSAddr
= ConfigFile
.getVar("LSHost").asString();
1266 // use the command line param if set
1267 uint shardId
= atoi(IService::getInstance()->getArg('S').c_str());
1269 nlinfo("Using shard id %u from command line '%s'", shardId
, IService::getInstance()->getArg('S').c_str());
1270 anticipateShardId(shardId
);
1272 else if (ConfigFile
.exists ("ShardId"))
1274 // use the config file param if set
1275 uint shardId
= IService::getInstance()->ConfigFile
.getVar ("ShardId").asInt();
1277 nlinfo("Using shard id %u from config file '%s'", shardId
, IService::getInstance()->ConfigFile
.getVar ("ShardId").asString().c_str());
1278 anticipateShardId(shardId
);
1281 // the config file must have a valid address where the login service is
1282 nlassert(!LSAddr
.empty());
1284 // add default port if not set by the config file
1285 if (LSAddr
.find (":") == string::npos
)
1288 AllowDispatchMsgToLS
= true;
1290 if (ConfigFile
.getVarPtr("DontUseLSService") == NULL
1291 || !ConfigFile
.getVar("DontUseLSService").asBool())
1293 // We are using NeL Login Service
1294 CUnifiedNetwork::getInstance()->addCallbackArray(LSCallbackArray
, sizeof(LSCallbackArray
)/sizeof(LSCallbackArray
[0]));
1297 CUnifiedNetwork::getInstance()->setServiceUpCallback("LS", cbLSConnection
, NULL
);
1298 CUnifiedNetwork::getInstance()->addService("LS", LSAddr
);
1301 // List of expected service instances
1302 ConfigFile
.setCallback( "ExpectedServices", cbUpdateExpectedServices
);
1303 cbUpdateExpectedServices( ConfigFile
.getVar( "ExpectedServices" ) );
1307 * read config variable ShardOpenStateFile to update
1310 cbShardOpenStateFile(ShardOpenStateFile
);
1312 // // create a welcome service module (for SU comm)
1313 // IModuleManager::getInstance().createModule("WelcomeService", "ws", "");
1314 // // plug the module in the default gateway
1315 // NLMISC::CCommandRegistry::getInstance().execute("ws.plug wg", InfoLog());
1320 // update the service status
1322 removeStatusTag("DEV_ONLY");
1323 removeStatusTag("RESTRICTED");
1324 removeStatusTag("Open");
1327 addStatusTag("DEV_ONLY");
1328 else if (ShardOpen
== 1)
1329 addStatusTag("RESTRICTED");
1330 else if (ShardOpen
== 2)
1331 addStatusTag("Open");
1339 static const char* getCompleteServiceName(const IService
* theService
)
1341 static std::string s
;
1342 s
= "welcome_service";
1344 if (theService
->haveLongArg("wsname"))
1346 s
+= "_"+theService
->getLongArg("wsname");
1349 if (theService
->haveLongArg("fullwsname"))
1351 s
= theService
->getLongArg("fullwsname");
1357 static const char* getShortServiceName(const IService
* theService
)
1359 static std::string s
;
1362 if (theService
->haveLongArg("shortwsname"))
1364 s
= theService
->getLongArg("shortwsname");
1370 // Service instantiation
1371 NLNET_SERVICE_MAIN( CWelcomeService
, getShortServiceName(scn
), getCompleteServiceName(scn
), 0, FESCallbackArray
, NELNS_CONFIG
, NELNS_LOGS
);
1374 // welcome service module
1375 //class CWelcomeServiceMod :
1376 // public CEmptyModuleCommBehav<CEmptyModuleServiceBehav<CEmptySocketBehav<CModuleBase> > >,
1377 // public WS::CWelcomeServiceSkel
1379 // void onProcessModuleMessage(IModuleProxy *sender, const CMessage &message)
1381 // if (CWelcomeServiceSkel::onDispatchMessage(sender, message))
1384 // nlwarning("Unknown message '%s' received by '%s'",
1385 // message.getName().c_str(),
1386 // getModuleName().c_str());
1390 // ////// CWelcomeServiceSkel implementation
1392 // // ask the welcome service to welcome a user
1393 // 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)
1395 // string ret = lsChooseShard(userName,
1402 // if (!ret.empty())
1404 // // TODO : correct this
1406 // CWelcomeServiceClientProxy wsc(sender);
1407 // wsc.welcomeUserResult(this, userId, ret.empty(), fsAddr);
1411 // // ask the welcome service to disconnect a user
1412 // virtual void disconnectUser(NLNET::IModuleProxy *sender, uint32 userId)
1422 void CWelcomeServiceMod::onModuleUp(IModuleProxy
*proxy
)
1424 if (proxy
->getModuleClassName() == "RingSessionManager")
1426 if (_RingSessionManager
!= NULL
)
1428 nlwarning("WelcomeServiceMod::onModuleUp : receiving module up for RingSessionManager '%s', but already have it as '%s', replacing it",
1429 proxy
->getModuleName().c_str(),
1430 _RingSessionManager
->getModuleName().c_str());
1432 // store this module as the ring session manager
1433 _RingSessionManager
= proxy
;
1435 // say hello to our new friend (transmit fixed session id if set in config file)
1436 nlinfo("Registering welcome service module into session manager '%s'", proxy
->getModuleName().c_str());
1437 uint32 sessionId
= 0;
1438 CConfigFile::CVar
*varFixedSessionId
= IService::getInstance()->ConfigFile
.getVarPtr( "FixedSessionId" );
1439 if ( varFixedSessionId
)
1440 sessionId
= varFixedSessionId
->asInt();
1441 CWelcomeServiceClientProxy
wscp(proxy
);
1442 wscp
.registerWS(this, IService::getInstance()->getShardId(), sessionId
, OnlineServices
.getOnlineStatus());
1445 uint32 totalNbOnlineUsers
= 0, totalNbPendingUsers
= 0;
1446 for (list
<CFES
>::iterator it
=FESList
.begin(); it
!=FESList
.end(); ++it
)
1448 totalNbOnlineUsers
+= (*it
).NbUser
;
1449 totalNbPendingUsers
+= (*it
).NbPendingUsers
;
1451 CWelcomeServiceMod::getInstance()->updateConnectedPlayerCount(totalNbOnlineUsers
, totalNbPendingUsers
);
1453 else if (proxy
->getModuleClassName() == "LoginService")
1455 _LoginService
= proxy
;
1459 void CWelcomeServiceMod::onModuleDown(IModuleProxy
*proxy
)
1461 if (_RingSessionManager
== proxy
)
1463 // remove this module as the ring session manager
1464 _RingSessionManager
= NULL
;
1466 else if (_LoginService
== proxy
)
1467 _LoginService
= NULL
;
1471 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
)
1473 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
);
1474 string ret
= lsChooseShard(userName
,
1482 uint32 userId
= charId
>> 4;
1485 nldebug( "ERLOG: lsChooseShard returned an error => welcomeUserResult");
1486 // TODO : correct this
1488 CWelcomeServiceClientProxy
wsc(sender
);
1489 wsc
.welcomeUserResult(this, userId
, false, fsAddr
, ret
);
1493 nldebug( "ERLOG: lsChooseShard OK => adding to pending");
1494 TPendingFEResponseInfo pfri
;
1496 pfri
.UserId
= userId
;
1497 pfri
.WaiterModule
= sender
;
1498 PendingFeResponse
.insert(make_pair(cookie
, pfri
));
1502 void CWelcomeServiceMod::pendingUserLost(const NLNET::CLoginCookie
&cookie
)
1507 CLoginServiceProxy
ls(_LoginService
);
1509 ls
.pendingUserLost(this, cookie
);
1513 // register the module
1514 NLNET_REGISTER_MODULE_FACTORY(CWelcomeServiceMod
, "WelcomeService");
1523 NLMISC_DYNVARIABLE(uint32
, OnlineUsersNumber
, "number of connected users on this shard")
1525 // we can only read the value
1529 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end (); it
++)
1531 nbusers
+= (*it
).NbUser
;
1543 NLMISC_COMMAND (frontends
, "displays the list of all registered front ends", "")
1545 if(args
.size() != 0) return false;
1547 log
.displayNL ("Display the %d registered front end :", FESList
.size());
1548 for (list
<CFES
>::iterator it
= FESList
.begin(); it
!= FESList
.end (); it
++)
1550 // log.displayNL ("> FE %u: nb estimated users: %u nb users: %u, nb pending users : %u",
1551 log
.displayNL ("> FE %u: nb users: %u, nb pending users : %u",
1554 it
->NbPendingUsers
);
1556 log
.displayNL ("End ot the list");
1561 NLMISC_COMMAND (users
, "displays the list of all registered users", "")
1563 if(args
.size() != 0) return false;
1565 log
.displayNL ("Display the %d registered users :", UserIdSockAssociations
.size());
1566 for (map
<uint32
, TServiceId
>::iterator it
= UserIdSockAssociations
.begin(); it
!= UserIdSockAssociations
.end (); it
++)
1568 log
.displayNL ("> %u SId=%u", (*it
).first
, (*it
).second
.get());
1570 log
.displayNL ("End ot the list");
1575 NLMISC_COMMAND( displayOnlineServices
, "Display the online service instances", "" )
1577 OnlineServices
.display( log
);
1581 NLMISC_VARIABLE( bool, OnlineStatus
, "Main online status of the shard" );