/*************************************************************************** * Copyright (C) 2007 by Lothar May * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #define SERVER_CLOSE_SESSION_DELAY_SEC 1 #define SERVER_MAX_NUM_SESSIONS 512 // Maximum number of idle users in lobby. #define SERVER_CACHE_CLEANUP_INTERVAL_SEC 86400 // 1 day #define SERVER_INIT_SESSION_TIMEOUT_SEC 20 #define SERVER_COMPUTER_PLAYER_NAME "Computer" using namespace std; class ServerSenderCallback : public SenderCallback { public: ServerSenderCallback(ServerLobbyThread &server) : m_server(server) {} virtual ~ServerSenderCallback() {} virtual bool GetSocketForSession(SessionId session, SOCKET &outSocket) { return m_server.GetSocketForSession(session, outSocket); } virtual void SignalNetError(SessionId /*session*/, int /*errorID*/, int /*osErrorID*/) { // We just ignore send errors for now, on server side. // A serious send error should trigger a read error or a read // returning 0 afterwards, and we will handle this error. } private: ServerLobbyThread &m_server; }; ServerLobbyThread::ServerLobbyThread(GuiInterface &gui, ConfigFile *playerConfig, AvatarManager &avatarManager) : m_gui(gui), m_avatarManager(avatarManager), m_playerConfig(playerConfig), m_curGameId(0), m_curUniquePlayerId(0), m_curSessionId(INVALID_SESSION + 1), m_totalPlayersLoggedIn(0), m_totalGamesStarted(0) { m_senderCallback.reset(new ServerSenderCallback(*this)); m_sender.reset(new SenderThread(GetSenderCallback())); m_receiver.reset(new ReceiverHelper); } ServerLobbyThread::~ServerLobbyThread() { CleanupConnectQueue(); } void ServerLobbyThread::Init(const string &pwd) { m_password = pwd; } void ServerLobbyThread::AddConnection(boost::shared_ptr data) { boost::mutex::scoped_lock lock(m_connectQueueMutex); m_connectQueue.push_back(data); } void ServerLobbyThread::ReAddSession(SessionWrapper session, int reason) { boost::shared_ptr packet(new NetPacketRemovedFromGame); NetPacketRemovedFromGame::Data removedData; removedData.removeReason = reason; static_cast(packet.get())->SetData(removedData); GetSender().Send(session.sessionData->GetId(), packet); boost::mutex::scoped_lock lock(m_sessionQueueMutex); m_sessionQueue.push_back(session); } void ServerLobbyThread::MoveSessionToGame(ServerGameThread &game, SessionWrapper session) { // Remove session from the lobby. m_sessionManager.RemoveSession(session.sessionData->GetId()); // Session is now in game state. session.sessionData->SetState(SessionData::Game); // Store it in the list of game sessions. m_gameSessionManager.AddSession(session); // Add session to the game. game.AddSession(session); } void ServerLobbyThread::RemoveSessionFromGame(SessionWrapper session) { // Just remove the session. Only for fatal errors. CloseSessionDelayed(session); } void ServerLobbyThread::CloseSessionDelayed(SessionWrapper session) { m_initTimerSessionMap.erase(session.sessionData->GetId()); m_sessionManager.RemoveSession(session.sessionData->GetId()); m_gameSessionManager.RemoveSession(session.sessionData->GetId()); boost::timers::portable::microsec_timer closeTimer; CloseSessionList::value_type closeSessionData(closeTimer, session.sessionData); boost::mutex::scoped_lock lock(m_closeSessionListMutex); m_closeSessionList.push_back(closeSessionData); // Update stats (if needed). BroadcastStatisticsUpdate(); } void ServerLobbyThread::NotifyPlayerJoinedGame(unsigned gameId, unsigned playerId) { // Send notification to players in lobby. boost::shared_ptr packet(new NetPacketGameListPlayerJoined); NetPacketGameListPlayerJoined::Data packetData; packetData.gameId = gameId; packetData.playerId = playerId; static_cast(packet.get())->SetData(packetData); m_sessionManager.SendToAllSessions(GetSender(), packet, SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), packet, SessionData::Game); } void ServerLobbyThread::NotifyPlayerLeftGame(unsigned gameId, unsigned playerId) { // Send notification to players in lobby. boost::shared_ptr packet(new NetPacketGameListPlayerLeft); NetPacketGameListPlayerLeft::Data packetData; packetData.gameId = gameId; packetData.playerId = playerId; static_cast(packet.get())->SetData(packetData); m_sessionManager.SendToAllSessions(GetSender(), packet, SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), packet, SessionData::Game); } void ServerLobbyThread::NotifyGameAdminChanged(unsigned gameId, unsigned newAdminPlayerId) { // Send notification to players in lobby. boost::shared_ptr packet(new NetPacketGameListAdminChanged); NetPacketGameListAdminChanged::Data packetData; packetData.gameId = gameId; packetData.newAdminplayerId = newAdminPlayerId; static_cast(packet.get())->SetData(packetData); m_sessionManager.SendToAllSessions(GetSender(), packet, SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), packet, SessionData::Game); } void ServerLobbyThread::NotifyStartingGame(unsigned gameId) { boost::shared_ptr packet = CreateNetPacketGameListUpdate(gameId, GAME_MODE_STARTED); m_sessionManager.SendToAllSessions(GetSender(), packet, SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), packet, SessionData::Game); } void ServerLobbyThread::NotifyReopeningGame(unsigned gameId) { boost::shared_ptr packet = CreateNetPacketGameListUpdate(gameId, GAME_MODE_CREATED); m_sessionManager.SendToAllSessions(GetSender(), packet, SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), packet, SessionData::Game); } void ServerLobbyThread::HandleGameRetrievePlayerInfo(SessionWrapper session, const NetPacketRetrievePlayerInfo &tmpPacket) { // Someone within a game requested player info. HandleNetPacketRetrievePlayerInfo(session, tmpPacket); } void ServerLobbyThread::HandleGameRetrieveAvatar(SessionWrapper session, const NetPacketRetrieveAvatar &tmpPacket) { // Someone within a game requested an avatar. HandleNetPacketRetrieveAvatar(session, tmpPacket); } void ServerLobbyThread::AddComputerPlayer(boost::shared_ptr player) { boost::mutex::scoped_lock lock(m_computerPlayersMutex); m_computerPlayers.insert(PlayerDataMap::value_type(player->GetUniqueId(), player)); } void ServerLobbyThread::RemoveComputerPlayer(boost::shared_ptr player) { boost::mutex::scoped_lock lock(m_computerPlayersMutex); m_computerPlayers.erase(player->GetUniqueId()); } void ServerLobbyThread::RemoveGame(unsigned id) { boost::mutex::scoped_lock lock(m_removeGameListMutex); m_removeGameList.push_back(id); } bool ServerLobbyThread::GetSocketForSession(SessionId session, SOCKET &outSocket) { bool retVal = m_sessionManager.GetSocketForSession(session, outSocket); if (!retVal) retVal = m_gameSessionManager.GetSocketForSession(session, outSocket); return retVal; } AvatarManager & ServerLobbyThread::GetAvatarManager() { return m_avatarManager; } u_int32_t ServerLobbyThread::GetNextUniquePlayerId() { boost::mutex::scoped_lock lock(m_curUniquePlayerIdMutex); return m_curUniquePlayerId++; } u_int32_t ServerLobbyThread::GetNextGameId() { return m_curGameId++; } void ServerLobbyThread::Main() { GetSender().Run(); try { while (!ShouldTerminate()) { // Process new connections. NewConnectionLoop(); // Process re-added sessions. NewSessionLoop(); // Main loop. ProcessLoop(); // Close sessions. CloseSessionLoop(); // Remove games. RemoveGameLoop(); // Cleanup cache. CleanupAvatarCache(); } } catch (const PokerTHException &e) { GetCallback().SignalNetServerError(e.GetErrorId(), e.GetOsErrorCode()); LOG_ERROR(e.what()); } TerminateGames(); GetSender().SignalTermination(); GetSender().Join(SENDER_THREAD_TERMINATE_TIMEOUT); CleanupConnectQueue(); } void ServerLobbyThread::ProcessLoop() { // Wait for data. SessionWrapper session = m_sessionManager.Select(RECV_TIMEOUT_MSEC); if (session.sessionData.get()) { boost::shared_ptr packet; try { // Receive the next packet. packet = GetReceiver().Recv(session.sessionData->GetSocket(), session.sessionData->GetReceiveBuffer()); } catch (const NetException &) { // On error: Close this session. CloseSessionDelayed(session); return; } if (packet.get()) { if (session.sessionData->GetState() == SessionData::Init) { if (packet->ToNetPacketInit()) HandleNetPacketInit(session, *packet->ToNetPacketInit()); else if (packet->ToNetPacketAvatarHeader()) HandleNetPacketAvatarHeader(session, *packet->ToNetPacketAvatarHeader()); else if (packet->ToNetPacketUnknownAvatar()) HandleNetPacketUnknownAvatar(session, *packet->ToNetPacketUnknownAvatar()); else SessionError(session, ERR_SOCK_INVALID_STATE); } else if (session.sessionData->GetState() == SessionData::ReceivingAvatar) { if (packet->ToNetPacketAvatarFile()) HandleNetPacketAvatarFile(session, *packet->ToNetPacketAvatarFile()); else if (packet->ToNetPacketAvatarEnd()) HandleNetPacketAvatarEnd(session, *packet->ToNetPacketAvatarEnd()); else SessionError(session, ERR_SOCK_INVALID_STATE); } else { if (packet->ToNetPacketRetrievePlayerInfo()) HandleNetPacketRetrievePlayerInfo(session, *packet->ToNetPacketRetrievePlayerInfo()); else if (packet->ToNetPacketRetrieveAvatar()) HandleNetPacketRetrieveAvatar(session, *packet->ToNetPacketRetrieveAvatar()); else if (packet->ToNetPacketCreateGame()) HandleNetPacketCreateGame(session, *packet->ToNetPacketCreateGame()); else if (packet->ToNetPacketJoinGame()) HandleNetPacketJoinGame(session, *packet->ToNetPacketJoinGame()); else SessionError(session, ERR_SOCK_INVALID_STATE); } } } } void ServerLobbyThread::HandleNetPacketInit(SessionWrapper session, const NetPacketInit &tmpPacket) { NetPacketInit::Data initData; tmpPacket.GetData(initData); // Check the protocol version. if (initData.versionMajor != NET_VERSION_MAJOR) { SessionError(session, ERR_NET_VERSION_NOT_SUPPORTED); return; } // Check the server password. if (!CheckPassword(initData.password)) { SessionError(session, ERR_NET_INVALID_PASSWORD); return; } // Check whether the player name is correct. // Partly, this is also done in netpacket. // However, some disallowed names are checked only here. if (initData.playerName.empty() || initData.playerName.size() > MAX_NAME_SIZE || initData.playerName[0] == '#' || initData.playerName[0] == ' ' || initData.playerName.substr(0, sizeof(SERVER_COMPUTER_PLAYER_NAME) - 1) == SERVER_COMPUTER_PLAYER_NAME) { SessionError(session, ERR_NET_INVALID_PLAYER_NAME); return; } // Check whether this player is already connected. if (IsPlayerConnected(initData.playerName)) { SessionError(session, ERR_NET_PLAYER_NAME_IN_USE); return; } // Create player data object. boost::shared_ptr tmpPlayerData( new PlayerData(GetNextUniquePlayerId(), 0, PLAYER_TYPE_HUMAN, PLAYER_RIGHTS_NORMAL)); tmpPlayerData->SetName(initData.playerName); tmpPlayerData->SetNetSessionData(session.sessionData); if (initData.showAvatar) tmpPlayerData->SetAvatarMD5(initData.avatar); // Set player data for session. m_sessionManager.SetSessionPlayerData(session.sessionData->GetId(), tmpPlayerData); session.playerData = tmpPlayerData; if (initData.showAvatar && !GetAvatarManager().HasAvatar(initData.avatar)) RequestPlayerAvatar(session); else EstablishSession(session); } void ServerLobbyThread::HandleNetPacketAvatarHeader(SessionWrapper session, const NetPacketAvatarHeader &tmpPacket) { if (session.playerData.get()) { NetPacketAvatarHeader::Data headerData; tmpPacket.GetData(headerData); if (headerData.avatarFileSize >= MIN_AVATAR_FILE_SIZE && headerData.avatarFileSize <= MAX_AVATAR_FILE_SIZE) { boost::shared_ptr tmpAvatarData(new AvatarData); tmpAvatarData->fileData.reserve(headerData.avatarFileSize); tmpAvatarData->fileType = headerData.avatarFileType; tmpAvatarData->reportedSize = headerData.avatarFileSize; // Ignore request id for now. session.playerData->SetNetAvatarData(tmpAvatarData); // Session is now receiving an avatar. session.sessionData->SetState(SessionData::ReceivingAvatar); } else SessionError(session, ERR_NET_AVATAR_TOO_LARGE); } } void ServerLobbyThread::HandleNetPacketUnknownAvatar(SessionWrapper session, const NetPacketUnknownAvatar &/*tmpPacket*/) { if (session.playerData.get()) { // Free memory (just in case). session.playerData->SetNetAvatarData(boost::shared_ptr()); // Start session. EstablishSession(session); } } void ServerLobbyThread::HandleNetPacketAvatarFile(SessionWrapper session, const NetPacketAvatarFile &tmpPacket) { if (session.playerData.get()) { NetPacketAvatarFile::Data data; tmpPacket.GetData(data); boost::shared_ptr tmpAvatar = session.playerData->GetNetAvatarData(); if (tmpAvatar.get() && tmpAvatar->fileData.size() + data.fileData.size() <= tmpAvatar->reportedSize) { std::copy(data.fileData.begin(), data.fileData.end(), back_inserter(tmpAvatar->fileData)); } } } void ServerLobbyThread::HandleNetPacketAvatarEnd(SessionWrapper session, const NetPacketAvatarEnd &/*tmpPacket*/) { if (session.playerData.get()) { boost::shared_ptr tmpAvatar = session.playerData->GetNetAvatarData(); MD5Buf avatarMD5 = session.playerData->GetAvatarMD5(); if (!avatarMD5.IsZero() && tmpAvatar.get()) { unsigned avatarSize = (unsigned)tmpAvatar->fileData.size(); if (avatarSize == tmpAvatar->reportedSize) { if (!GetAvatarManager().StoreAvatarInCache(avatarMD5, tmpAvatar->fileType, &tmpAvatar->fileData[0], avatarSize)) LOG_ERROR("Failed to store avatar in cache directory."); // Free memory. session.playerData->SetNetAvatarData(boost::shared_ptr()); // Init finished - start session. EstablishSession(session); } else SessionError(session, ERR_NET_WRONG_AVATAR_SIZE); } } } void ServerLobbyThread::HandleNetPacketRetrievePlayerInfo(SessionWrapper session, const NetPacketRetrievePlayerInfo &tmpPacket) { NetPacketRetrievePlayerInfo::Data request; tmpPacket.GetData(request); // Find player in lobby or in a game. boost::shared_ptr tmpPlayer = m_sessionManager.GetSessionByUniquePlayerId(request.playerId).playerData; if (!tmpPlayer.get()) tmpPlayer = m_gameSessionManager.GetSessionByUniquePlayerId(request.playerId).playerData; if (!tmpPlayer.get()) { boost::mutex::scoped_lock lock(m_computerPlayersMutex); PlayerDataMap::const_iterator pos = m_computerPlayers.find(request.playerId); if (pos != m_computerPlayers.end()) tmpPlayer = pos->second; } if (tmpPlayer.get()) { // Send player info to client. boost::shared_ptr info(new NetPacketPlayerInfo); NetPacketPlayerInfo::Data infoData; infoData.playerId = tmpPlayer->GetUniqueId(); infoData.playerInfo.ptype = tmpPlayer->GetType(); infoData.playerInfo.playerName = tmpPlayer->GetName(); infoData.playerInfo.hasAvatar = !tmpPlayer->GetAvatarMD5().IsZero(); if (infoData.playerInfo.hasAvatar) infoData.playerInfo.avatar = tmpPlayer->GetAvatarMD5(); static_cast(info.get())->SetData(infoData); GetSender().Send(session.sessionData->GetId(), info); } else { // Unknown player id - notify client. boost::shared_ptr unknown(new NetPacketUnknownPlayerId); NetPacketUnknownPlayerId::Data unknownData; unknownData.playerId = request.playerId; static_cast(unknown.get())->SetData(unknownData); GetSender().Send(session.sessionData->GetId(), unknown); } } void ServerLobbyThread::HandleNetPacketRetrieveAvatar(SessionWrapper session, const NetPacketRetrieveAvatar &tmpPacket) { bool avatarFound = false; NetPacketRetrieveAvatar::Data request; tmpPacket.GetData(request); string tmpFile; if (GetAvatarManager().GetAvatarFileName(request.avatar, tmpFile)) { NetPacketList tmpPackets; if (GetAvatarManager().AvatarFileToNetPackets(tmpFile, request.requestId, tmpPackets) == 0) { avatarFound = true; GetSender().SendLowPrio(session.sessionData->GetId(), tmpPackets); } else LOG_ERROR("Failed to read avatar file for network transmission."); } if (!avatarFound) { // Notify client we didn't find the avatar. boost::shared_ptr unknown(new NetPacketUnknownAvatar); NetPacketUnknownAvatar::Data unknownData; unknownData.requestId = request.requestId; static_cast(unknown.get())->SetData(unknownData); GetSender().Send(session.sessionData->GetId(), unknown); } } void ServerLobbyThread::HandleNetPacketCreateGame(SessionWrapper session, const NetPacketCreateGame &tmpPacket) { // Create a new game. NetPacketCreateGame::Data createGameData; tmpPacket.GetData(createGameData); boost::shared_ptr game( new ServerGameThread( *this, GetNextGameId(), createGameData.gameName, createGameData.password, createGameData.gameData, session.playerData->GetUniqueId(), GetGui(), m_playerConfig)); MoveSessionToGame(*game, session); // Add game to list of games. InternalAddGame(game); // Start the game. game->Run(); } void ServerLobbyThread::HandleNetPacketJoinGame(SessionWrapper session, const NetPacketJoinGame &tmpPacket) { // Join an existing game. NetPacketJoinGame::Data joinGameData; tmpPacket.GetData(joinGameData); GameMap::iterator pos = m_gameMap.find(joinGameData.gameId); if (pos != m_gameMap.end()) { ServerGameThread &game = *pos->second; if (game.CheckPassword(joinGameData.password)) { MoveSessionToGame(game, session); } else { SendJoinGameFailed(session.sessionData->GetId(), NTF_NET_JOIN_INVALID_PASSWORD); } } else { SessionError(session, ERR_NET_UNKNOWN_GAME); } } void ServerLobbyThread::EstablishSession(SessionWrapper session) { assert(session.playerData.get()); // Send ACK to client. boost::shared_ptr initAck(new NetPacketInitAck); NetPacketInitAck::Data initAckData; initAckData.latestGameVersion = POKERTH_VERSION; initAckData.latestBetaRevision = POKERTH_BETA_REVISION; initAckData.sessionId = session.sessionData->GetId(); // TODO: currently unused. initAckData.playerId = session.playerData->GetUniqueId(); static_cast(initAck.get())->SetData(initAckData); GetSender().Send(session.sessionData->GetId(), initAck); // Send the game list to the client. SendGameList(session.sessionData->GetId()); // Session is now established. m_initTimerSessionMap.erase(session.sessionData->GetId()); session.sessionData->SetState(SessionData::Established); ++m_totalPlayersLoggedIn; BroadcastStatisticsUpdate(); } void ServerLobbyThread::RequestPlayerAvatar(SessionWrapper session) { assert(session.playerData.get()); // Ask the client to send its avatar. boost::shared_ptr retrieveAvatar(new NetPacketRetrieveAvatar); NetPacketRetrieveAvatar::Data retrieveAvatarData; retrieveAvatarData.requestId = session.playerData->GetUniqueId(); retrieveAvatarData.avatar = session.playerData->GetAvatarMD5(); static_cast(retrieveAvatar.get())->SetData(retrieveAvatarData); GetSender().Send(session.sessionData->GetId(), retrieveAvatar); } void ServerLobbyThread::NewConnectionLoop() { // Handle one incoming connection at a time. boost::shared_ptr tmpData; { boost::mutex::scoped_lock lock(m_connectQueueMutex); if (!m_connectQueue.empty()) { tmpData = m_connectQueue.front(); m_connectQueue.pop_front(); } } if (tmpData.get()) HandleNewConnection(tmpData); } void ServerLobbyThread::NewSessionLoop() { // Handle one incoming session at a time. SessionWrapper tmpSession; { boost::mutex::scoped_lock lock(m_sessionQueueMutex); if (!m_sessionQueue.empty()) { tmpSession = m_sessionQueue.front(); m_sessionQueue.pop_front(); } } if (tmpSession.sessionData.get() && tmpSession.playerData.get()) HandleReAddedSession(tmpSession); } void ServerLobbyThread::CloseSessionLoop() { { InitTimerSessionMap::iterator i = m_initTimerSessionMap.begin(); InitTimerSessionMap::iterator end = m_initTimerSessionMap.end(); // Remove sessions if they do not initialize within a certain period. while (i != end) { InitTimerSessionMap::iterator next = i; ++next; if (i->second.elapsed().total_seconds() > SERVER_INIT_SESSION_TIMEOUT_SEC) { m_sessionManager.RemoveSession(i->first); m_initTimerSessionMap.erase(i); } i = next; } } { boost::mutex::scoped_lock lock(m_closeSessionListMutex); CloseSessionList::iterator i = m_closeSessionList.begin(); CloseSessionList::iterator end = m_closeSessionList.end(); while (i != end) { CloseSessionList::iterator cur = i++; if (cur->first.elapsed().total_seconds() >= SERVER_CLOSE_SESSION_DELAY_SEC) m_closeSessionList.erase(cur); } } } void ServerLobbyThread::RemoveGameLoop() { boost::mutex::scoped_lock lock(m_removeGameListMutex); RemoveGameList::iterator i = m_removeGameList.begin(); RemoveGameList::iterator end = m_removeGameList.end(); // Synchronously remove games which have been closed. while (i != end) { GameMap::iterator pos = m_gameMap.find(*i); if (pos != m_gameMap.end()) { boost::shared_ptr tmpGame = pos->second; tmpGame->SignalTermination(); tmpGame->Join(GAME_THREAD_TERMINATE_TIMEOUT); InternalRemoveGame(tmpGame); } ++i; } m_removeGameList.clear(); } void ServerLobbyThread::CleanupAvatarCache() { // Only act on timer and if there are no sessions. if (m_cacheCleanupTimer.elapsed().total_seconds() > SERVER_CACHE_CLEANUP_INTERVAL_SEC && !m_sessionManager.HasSessions() && !m_gameSessionManager.HasSessions()) { m_avatarManager.RemoveOldAvatarCacheEntries(); m_cacheCleanupTimer.reset(); m_cacheCleanupTimer.start(); } } void ServerLobbyThread::InternalAddGame(boost::shared_ptr game) { // Add game to list. m_gameMap.insert(GameMap::value_type(game->GetId(), game)); // Notify all players. m_sessionManager.SendToAllSessions(GetSender(), CreateNetPacketGameListNew(*game), SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), CreateNetPacketGameListNew(*game), SessionData::Game); ++m_totalGamesStarted; BroadcastStatisticsUpdate(); } void ServerLobbyThread::InternalRemoveGame(boost::shared_ptr game) { // Remove game from list. m_gameMap.erase(game->GetId()); // Remove all sessions left in the game. game->RemoveAllSessions(); // Notify all players. boost::shared_ptr packet = CreateNetPacketGameListUpdate(game->GetId(), GAME_MODE_CLOSED); m_sessionManager.SendToAllSessions(GetSender(), packet, SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), packet, SessionData::Game); } void ServerLobbyThread::TerminateGames() { GameMap::iterator i = m_gameMap.begin(); GameMap::iterator end = m_gameMap.end(); while (i != end) { i->second->SignalTermination(); i->second->Join(GAME_THREAD_TERMINATE_TIMEOUT); ++i; } m_gameMap.clear(); } void ServerLobbyThread::HandleNewConnection(boost::shared_ptr connData) { // Create a random session id. // This id can be used to reconnect to the server if the connection was lost. //unsigned sessionId; // TODO: use randomized method. //if(!RAND_bytes((unsigned char *)&sessionId, sizeof(sessionId))) //{ // RAND_pseudo_bytes((unsigned char *)&sessionId, sizeof(sessionId)); //} // Create a new session. boost::shared_ptr sessionData(new SessionData(connData->ReleaseSocket(), m_curSessionId++)); m_sessionManager.AddSession(sessionData); if (m_sessionManager.GetRawSessionCount() <= SERVER_MAX_NUM_SESSIONS) { m_initTimerSessionMap[sessionData->GetId()] = boost::timers::portable::microsec_timer(); } else { // Server is full. // Gracefully close this session. SessionError(SessionWrapper(sessionData, boost::shared_ptr()), ERR_NET_SERVER_FULL); } } void ServerLobbyThread::HandleReAddedSession(SessionWrapper session) { // Remove session from game session list. m_gameSessionManager.RemoveSession(session.sessionData->GetId()); if (m_sessionManager.GetRawSessionCount() <= SERVER_MAX_NUM_SESSIONS) { // Set state (back) to established. session.sessionData->SetState(SessionData::Established); // Add session to lobby list. m_sessionManager.AddSession(session); } else { // Gracefully close this session. SessionError(session, ERR_NET_SERVER_FULL); } } void ServerLobbyThread::CleanupConnectQueue() { boost::mutex::scoped_lock lock(m_connectQueueMutex); // Sockets will be closed automatically. m_connectQueue.clear(); } void ServerLobbyThread::SessionError(SessionWrapper session, int errorCode) { if (session.sessionData.get()) { SendError(session.sessionData->GetId(), errorCode); CloseSessionDelayed(session); } } void ServerLobbyThread::SendError(SessionId s, int errorCode) { boost::shared_ptr packet(new NetPacketError); NetPacketError::Data errorData; errorData.errorCode = errorCode; static_cast(packet.get())->SetData(errorData); GetSender().Send(s, packet); } void ServerLobbyThread::SendJoinGameFailed(SessionId s, int reason) { boost::shared_ptr packet(new NetPacketJoinGameFailed); NetPacketJoinGameFailed::Data failedData; failedData.failureCode = reason; static_cast(packet.get())->SetData(failedData); GetSender().Send(s, packet); } void ServerLobbyThread::SendGameList(SessionId s) { GameMap::const_iterator game_i = m_gameMap.begin(); GameMap::const_iterator game_end = m_gameMap.end(); while (game_i != game_end) { GetSender().Send(s, CreateNetPacketGameListNew(*game_i->second)); ++game_i; } } void ServerLobbyThread::BroadcastStatisticsUpdate() { boost::shared_ptr packet(new NetPacketStatisticsChanged); NetPacketStatisticsChanged::Data statData; unsigned curNumberOfPlayersOnServer = m_sessionManager.GetRawSessionCount() + m_gameSessionManager.GetRawSessionCount(); if (curNumberOfPlayersOnServer != m_lastStatData.numberOfPlayersOnServer) m_lastStatData.numberOfPlayersOnServer = statData.stats.numberOfPlayersOnServer = curNumberOfPlayersOnServer; if (m_totalPlayersLoggedIn != m_lastStatData.totalPlayersEverLoggedIn) m_lastStatData.totalPlayersEverLoggedIn = statData.stats.totalPlayersEverLoggedIn = m_totalPlayersLoggedIn; if (m_totalGamesStarted != m_lastStatData.totalGamesEverStarted) m_lastStatData.totalGamesEverStarted = statData.stats.totalGamesEverStarted = m_totalGamesStarted; if (curNumberOfPlayersOnServer) { try { static_cast(packet.get())->SetData(statData); m_sessionManager.SendToAllSessions(GetSender(), packet, SessionData::Established); m_gameSessionManager.SendToAllSessions(GetSender(), packet, SessionData::Game); } catch (const NetException &) { // Ignore errors for now. //LOG_ERROR("ServerLobbyThread::BroadcastStatisticsUpdate: " << e.what()); } } } ServerCallback & ServerLobbyThread::GetCallback() { return m_gui; } SenderThread & ServerLobbyThread::GetSender() { assert(m_sender.get()); return *m_sender; } ReceiverHelper & ServerLobbyThread::GetReceiver() { assert(m_receiver.get()); return *m_receiver; } bool ServerLobbyThread::CheckPassword(const string &password) const { return (password == m_password); } ServerSenderCallback & ServerLobbyThread::GetSenderCallback() { assert(m_senderCallback.get()); return *m_senderCallback; } GuiInterface & ServerLobbyThread::GetGui() { return m_gui; } bool ServerLobbyThread::IsPlayerConnected(const string &name) { bool retVal = false; retVal = m_sessionManager.IsPlayerConnected(name); if (!retVal) retVal = m_gameSessionManager.IsPlayerConnected(name); return retVal; } boost::shared_ptr ServerLobbyThread::CreateNetPacketGameListNew(const ServerGameThread &game) { boost::shared_ptr packet(new NetPacketGameListNew); NetPacketGameListNew::Data packetData; packetData.gameId = game.GetId(); packetData.gameInfo.adminPlayerId = game.GetAdminPlayerId(); packetData.gameInfo.mode = game.IsRunning() ? GAME_MODE_STARTED : GAME_MODE_CREATED; packetData.gameInfo.name = game.GetName(); packetData.gameInfo.data = game.GetGameData(); packetData.gameInfo.players = game.GetPlayerIdList(); packetData.gameInfo.isPasswordProtected = game.IsPasswordProtected(); static_cast(packet.get())->SetData(packetData); return packet; } boost::shared_ptr ServerLobbyThread::CreateNetPacketGameListUpdate(unsigned gameId, GameMode mode) { boost::shared_ptr packet(new NetPacketGameListUpdate); NetPacketGameListUpdate::Data packetData; packetData.gameId = gameId; packetData.gameMode = mode; static_cast(packet.get())->SetData(packetData); return packet; }