/*************************************************************************** * 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 #define SERVER_CLOSE_SESSION_DELAY_SEC 10 #define SERVER_MAX_NUM_SESSIONS 64 // Maximum number of idle users in lobby. #define SERVER_COMPUTER_PLAYER_NAME "Computer" using namespace std; class ServerSenderCallback : public SenderCallback { public: ServerSenderCallback(ServerLobbyThread &server) : m_server(server) {} virtual ~ServerSenderCallback() {} virtual void SignalNetError(SOCKET sock, 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) : m_gui(gui), m_playerConfig(playerConfig), m_curUniquePlayerId(0), m_curGameId(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::CloseSessionDelayed(SessionWrapper session) { m_sessionManager.RemoveSession(session.sessionData->GetSocket()); boost::microsec_timer closeTimer; closeTimer.start(); CloseSessionList::value_type closeSessionData(closeTimer, session.sessionData); boost::mutex::scoped_lock lock(m_closeSessionListMutex); m_closeSessionList.push_back(closeSessionData); } void ServerLobbyThread::RemoveGame(unsigned id) { boost::mutex::scoped_lock lock(m_removeGameListMutex); m_removeGameList.push_back(id); } u_int32_t ServerLobbyThread::GetNextUniquePlayerId() { return m_curUniquePlayerId++; } u_int32_t ServerLobbyThread::GetNextGameId() { return m_curGameId++; } void ServerLobbyThread::Main() { GetSender().Run(); try { while (!ShouldTerminate()) { { // 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); } // Process loop. ProcessLoop(); // Close sessions. CloseSessionLoop(); // Remove games. RemoveGameLoop(); } } catch (const NetException &e) { GetCallback().SignalNetServerError(e.GetErrorId(), e.GetOsErrorCode()); } TerminateGames(); GetSender().SignalTermination(); GetSender().Join(SENDER_THREAD_TERMINATE_TIMEOUT); CleanupConnectQueue(); m_sessionManager.Clear(); } 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()); } catch (const NetException &) { // On error: Close this session. CloseSessionDelayed(session); return; } if (packet.get()) { if (packet->ToNetPacketInit()) { // Session should be in initial state. if (session.sessionData->GetState() != SessionData::Init) SessionError(session, ERR_SOCK_INVALID_STATE); else HandleNetPacketInit(session, *packet->ToNetPacketInit()); } // Session should be established. else if (session.sessionData->GetState() != SessionData::Established) SessionError(session, ERR_SOCK_INVALID_STATE); else { if (packet->ToNetPacketRetrievePlayerInfo()) HandleNetPacketRetrievePlayerInfo(session, *packet->ToNetPacketRetrievePlayerInfo()); else if (packet->ToNetPacketCreateGame()) HandleNetPacketCreateGame(session, *packet->ToNetPacketCreateGame()); else if (packet->ToNetPacketJoinGame()) HandleNetPacketJoinGame(session, *packet->ToNetPacketJoinGame()); } } } } 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.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); // Send ACK to client. boost::shared_ptr initAck(new NetPacketInitAck); NetPacketInitAck::Data initAckData; initAckData.sessionId = session.sessionData->GetId(); // TODO: currently unused. initAckData.playerId = tmpPlayerData->GetUniqueId(); static_cast(initAck.get())->SetData(initAckData); GetSender().Send(session.sessionData->GetSocket(), initAck); // Send the game list to the client. GameMap::const_iterator game_i = m_gameMap.begin(); GameMap::const_iterator game_end = m_gameMap.end(); while (game_i != game_end) { GetSender().Send(session.sessionData->GetSocket(), CreateNetPacketGameListNew(*game_i->second)); ++game_i; } // Set player data for session. m_sessionManager.SetSessionPlayerData(session.sessionData->GetSocket(), tmpPlayerData); // Session is now established. session.sessionData->SetState(SessionData::Established); } void ServerLobbyThread::HandleNetPacketRetrievePlayerInfo(SessionWrapper session, const NetPacketRetrievePlayerInfo &tmpPacket) { NetPacketRetrievePlayerInfo::Data request; tmpPacket.GetData(request); // Find player in lobby or in a game. SessionWrapper tmpSession = m_sessionManager.GetSessionByUniquePlayerId(request.playerId); if (!tmpSession.sessionData.get() || !tmpSession.playerData.get()) { GameMap::const_iterator game_i = m_gameMap.begin(); GameMap::const_iterator game_end = m_gameMap.end(); while (game_i != game_end) { tmpSession = game_i->second->GetSessionManager().GetSessionByUniquePlayerId(request.playerId); if (tmpSession.sessionData.get() && tmpSession.playerData.get()) break; ++game_i; } } if (tmpSession.sessionData.get() && tmpSession.playerData.get()) { // Send player info to client. boost::shared_ptr info(new NetPacketPlayerInfo); NetPacketPlayerInfo::Data infoData; infoData.playerId = tmpSession.playerData->GetUniqueId(); infoData.playerInfo.ptype = tmpSession.playerData->GetType(); infoData.playerInfo.playerName = tmpSession.playerData->GetName(); static_cast(info.get())->SetData(infoData); GetSender().Send(session.sessionData->GetSocket(), info); } // TODO: handle error } 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, GetGui(), m_playerConfig)); game->Init(createGameData.password, createGameData.gameData); // Remove session from the lobby. m_sessionManager.RemoveSession(session.sessionData->GetSocket()); // Add session to the game. game->AddSession(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)) { // Remove session from the lobby. m_sessionManager.RemoveSession(session.sessionData->GetSocket()); // Add session to the game. game.AddSession(session); } else { SessionError(session, ERR_NET_INVALID_PASSWORD); } } else { SessionError(session, ERR_NET_UNKNOWN_GAME); } } void ServerLobbyThread::CloseSessionLoop() { 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::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); } void ServerLobbyThread::InternalRemoveGame(boost::shared_ptr game) { // Remove game from list. m_gameMap.erase(game->GetId()); // Notify all players. m_sessionManager.SendToAllSessions(GetSender(), CreateNetPacketGameListUpdate(*game, GAME_MODE_CLOSED), SessionData::Established); } 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) { if (m_sessionManager.GetRawSessionCount() <= SERVER_MAX_NUM_SESSIONS) { // Create a random session id. // This id can be used to reconnect to the server if the connection was lost. unsigned sessionId; RandomBytes((unsigned char *)&sessionId, sizeof(sessionId)); // TODO: check for collisions. // Create a new session. boost::shared_ptr sessionData(new SessionData(connData->ReleaseSocket(), sessionId)); m_sessionManager.AddSession(sessionData); } else { // Server is full. // Create a generic session with Id 0. boost::shared_ptr sessionData(new SessionData(connData->ReleaseSocket(), 0)); // Gracefully close this session. SessionError(SessionWrapper(sessionData, boost::shared_ptr()), 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->GetSocket(), errorCode); CloseSessionDelayed(session); } } void ServerLobbyThread::SendError(SOCKET 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); } 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) { GameMap::const_iterator game_i = m_gameMap.begin(); GameMap::const_iterator game_end = m_gameMap.end(); while (game_i != game_end) { if (game_i->second->GetSessionManager().IsPlayerConnected(name)) { retVal = true; break; } ++game_i; } } 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.mode = GAME_MODE_CREATED; packetData.gameInfo.name = game.GetName(); packetData.gameInfo.data = game.GetGameData(); packetData.gameInfo.players = game.GetSessionManager().GetPlayerIdList(); static_cast(packet.get())->SetData(packetData); return packet; } boost::shared_ptr ServerLobbyThread::CreateNetPacketGameListUpdate(const ServerGameThread &game, GameMode mode) { boost::shared_ptr packet(new NetPacketGameListUpdate); NetPacketGameListUpdate::Data packetData; packetData.gameId = game.GetId(); packetData.gameMode = mode; static_cast(packet.get())->SetData(packetData); return packet; }