diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 3a597589..260e3986 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -1,7 +1,7 @@ #include #include #include "remoteclient.h" - +#include "settingscache.h" #include "pending_command.h" #include "pb/commands.pb.h" #include "pb/session_commands.pb.h" @@ -16,8 +16,10 @@ static const unsigned int protocolVersion = 14; RemoteClient::RemoteClient(QObject *parent) : AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false), messageLength(0) { + + int keepalive = settingsCache->getKeepAlive(); timer = new QTimer(this); - timer->setInterval(1000); + timer->setInterval(keepalive * 1000); connect(timer, SIGNAL(timeout()), this, SLOT(ping())); socket = new QTcpSocket(this); @@ -102,11 +104,11 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica void RemoteClient::doLogin() { setStatus(StatusLoggingIn); - + Command_Login cmdLogin; cmdLogin.set_user_name(userName.toStdString()); cmdLogin.set_password(password.toStdString()); - + PendingCommand *pend = prepareSessionCommand(cmdLogin); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response))); sendCommand(pend); @@ -123,12 +125,12 @@ void RemoteClient::loginResponse(const Response &response) if (response.response_code() == Response::RespOk) { setStatus(StatusLoggedIn); emit userInfoChanged(resp.user_info()); - + QList buddyList; for (int i = resp.buddy_list_size() - 1; i >= 0; --i) buddyList.append(resp.buddy_list(i)); emit buddyListReceived(buddyList); - + QList ignoreList; for (int i = resp.ignore_list_size() - 1; i >= 0; --i) ignoreList.append(resp.ignore_list(i)); @@ -177,7 +179,7 @@ void RemoteClient::readData() QByteArray data = socket->readAll(); inputBuffer.append(data); - + do { if (!messageInProgress) { if (inputBuffer.size() >= 4) { @@ -202,7 +204,7 @@ void RemoteClient::readData() } if (inputBuffer.size() < messageLength) return; - + ServerMessage newServerMessage; newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); #ifdef QT_DEBUG @@ -210,9 +212,9 @@ void RemoteClient::readData() #endif inputBuffer.remove(0, messageLength); messageInProgress = false; - + processProtocolItem(newServerMessage); - + if (getStatus() == StatusDisconnecting) // use thread-safe getter doDisconnectFromServer(); } while (!inputBuffer.isEmpty()); @@ -231,14 +233,14 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont) buf.data()[2] = (unsigned char) (size >> 8); buf.data()[1] = (unsigned char) (size >> 16); buf.data()[0] = (unsigned char) (size >> 24); - + socket->write(buf); } void RemoteClient::doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password) { doDisconnectFromServer(); - + userName = _userName; password = _password; lastHostname = hostname; @@ -251,7 +253,7 @@ void RemoteClient::doConnectToServer(const QString &hostname, unsigned int port, void RemoteClient::doRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname) { doDisconnectFromServer(); - + userName = _userName; password = _password; email = _email; @@ -268,7 +270,7 @@ void RemoteClient::doRegisterToServer(const QString &hostname, unsigned int port void RemoteClient::doActivateToServer(const QString &_token) { doDisconnectFromServer(); - + token = _token; socket->connectToHost(lastHostname, lastPort); @@ -278,7 +280,7 @@ void RemoteClient::doActivateToServer(const QString &_token) void RemoteClient::doDisconnectFromServer() { timer->stop(); - + messageInProgress = false; handshakeStarted = false; messageLength = 0; @@ -289,7 +291,7 @@ void RemoteClient::doDisconnectFromServer() response.set_response_code(Response::RespNotConnected); response.set_cmd_id(pc[i]->getCommandContainer().cmd_id()); pc[i]->processResponse(response); - + delete pc[i]; } pendingCommands.clear(); @@ -309,9 +311,10 @@ void RemoteClient::ping() } } + int keepalive = settingsCache->getKeepAlive(); int maxTime = timeRunning - lastDataReceived; emit maxPingTime(maxTime, maxTimeout); - if (maxTime >= maxTimeout) { + if (maxTime >= (keepalive * maxTimeout)) { disconnectFromServer(); emit serverTimeout(); } else { diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 2ebe6815..9cdf7c8b 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -6,6 +6,7 @@ SettingsCache::SettingsCache() settings = new QSettings(this); lang = settings->value("personal/lang").toString(); + keepalive = settings->value("personal/keepalive", 5).toInt(); deckPath = settings->value("paths/decks").toString(); replaysPath = settings->value("paths/replays").toString(); @@ -429,7 +430,7 @@ QStringList SettingsCache::getCountries() const << "ad" << "ae" << "af" << "ag" << "ai" << "al" << "am" << "ao" << "aq" << "ar" << "as" << "at" << "au" << "aw" << "ax" << "az" << "ba" << "bb" << "bd" << "be" << "bf" << "bg" << "bh" << "bi" << "bj" << "bl" << "bm" << "bn" << "bo" << "bq" - << "br" << "bs" << "bt" << "bv" << "bw" << "by" << "bz" << "ca" << "cc" << "cd" + << "br" << "bs" << "bt" << "bv" << "bw" << "by" << "bz" << "ca" << "cc" << "cd" << "cf" << "cg" << "ch" << "ci" << "ck" << "cl" << "cm" << "cn" << "co" << "cr" << "cu" << "cv" << "cw" << "cx" << "cy" << "cz" << "de" << "dj" << "dk" << "dm" << "do" << "dz" << "ec" << "ee" << "eg" << "eh" << "er" << "es" << "et" << "fi" @@ -440,10 +441,10 @@ QStringList SettingsCache::getCountries() const << "je" << "jm" << "jo" << "jp" << "ke" << "kg" << "kh" << "ki" << "km" << "kn" << "kp" << "kr" << "kw" << "ky" << "kz" << "la" << "lb" << "lc" << "li" << "lk" << "lr" << "ls" << "lt" << "lu" << "lv" << "ly" << "ma" << "mc" << "md" << "me" - << "mf" << "mg" << "mh" << "mk" << "ml" << "mm" << "mn" << "mo" << "mp" << "mq" + << "mf" << "mg" << "mh" << "mk" << "ml" << "mm" << "mn" << "mo" << "mp" << "mq" << "mr" << "ms" << "mt" << "mu" << "mv" << "mw" << "mx" << "my" << "mz" << "na" << "nc" << "ne" << "nf" << "ng" << "ni" << "nl" << "no" << "np" << "nr" << "nu" - << "nz" << "om" << "pa" << "pe" << "pf" << "pg" << "ph" << "pk" << "pl" << "pm" + << "nz" << "om" << "pa" << "pe" << "pf" << "pg" << "ph" << "pk" << "pl" << "pm" << "pn" << "pr" << "ps" << "pt" << "pw" << "py" << "qa" << "re" << "ro" << "rs" << "ru" << "rw" << "sa" << "sb" << "sc" << "sd" << "se" << "sg" << "sh" << "si" << "sj" << "sk" << "sl" << "sm" << "sn" << "so" << "sr" << "ss" << "st" << "sv" @@ -507,4 +508,4 @@ void SettingsCache::setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEv { spectatorsCanSeeEverything = _spectatorsCanSeeEverything; settings->setValue("game/spectatorscanseeeverything", spectatorsCanSeeEverything); -} \ No newline at end of file +} diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index a4017638..14be4312 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -97,6 +97,7 @@ private: bool spectatorsNeedPassword; bool spectatorsCanTalk; bool spectatorsCanSeeEverything; + int keepalive; public: SettingsCache(); const QByteArray &getMainWindowGeometry() const { return mainWindowGeometry; } @@ -167,6 +168,7 @@ public: bool getSpectatorsNeedPassword() const { return spectatorsNeedPassword; } bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } bool getSpectatorsCanSeeEverything() const { return spectatorsCanSeeEverything; } + int getKeepAlive() const { return keepalive; } public slots: void setMainWindowGeometry(const QByteArray &_mainWindowGeometry); void setLang(const QString &_lang); diff --git a/common/server.h b/common/server.h index ad17bc53..a32c88fd 100644 --- a/common/server.h +++ b/common/server.h @@ -47,15 +47,16 @@ public: AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); const QMap &getRooms() { return rooms; } - + Server_AbstractUserInterface *findUser(const QString &userName) const; const QMap &getUsers() const { return users; } const QMap &getUsersBySessionId() const { return usersBySessionId; } void addClient(Server_ProtocolHandler *player); void removeClient(Server_ProtocolHandler *player); virtual QString getLoginMessage() const { return QString(); } - + virtual bool getGameShouldPing() const { return false; } + virtual int getPingClockInterval() const { return 0; } virtual int getMaxGameInactivityTime() const { return 9999999; } virtual int getMaxPlayerInactivityTime() const { return 9999999; } virtual int getMessageCountingInterval() const { return 0; } @@ -69,18 +70,18 @@ public: Server_DatabaseInterface *getDatabaseInterface() const; int getNextLocalGameId() { QMutexLocker locker(&nextLocalGameIdMutex); return ++nextLocalGameId; } - + void sendIsl_Response(const Response &item, int serverId = -1, qint64 sessionId = -1); void sendIsl_SessionEvent(const SessionEvent &item, int serverId = -1, qint64 sessionId = -1); void sendIsl_GameEventContainer(const GameEventContainer &item, int serverId = -1, qint64 sessionId = -1); void sendIsl_RoomEvent(const RoomEvent &item, int serverId = -1, qint64 sessionId = -1); void sendIsl_GameCommand(const CommandContainer &item, int serverId, qint64 sessionId, int roomId, int playerId); void sendIsl_RoomCommand(const CommandContainer &item, int serverId, qint64 sessionId, int roomId); - + void addExternalUser(const ServerInfo_User &userInfo); void removeExternalUser(const QString &userName); const QMap &getExternalUsers() const { return externalUsers; } - + void addPersistentPlayer(const QString &userName, int roomId, int gameId, int playerId); void removePersistentPlayer(const QString &userName, int roomId, int gameId, int playerId); QList getPersistentPlayerReferences(const QString &userName) const; @@ -90,7 +91,7 @@ private: mutable QReadWriteLock persistentPlayersLock; int nextLocalGameId; QMutex nextLocalGameIdMutex; -protected slots: +protected slots: void externalUserJoined(const ServerInfo_User &userInfo); void externalUserLeft(const QString &userName); void externalRoomUserJoined(int roomId, const ServerInfo_User &userInfo); @@ -101,7 +102,7 @@ protected slots: void externalGameCommandContainerReceived(const CommandContainer &cont, int playerId, int serverId, qint64 sessionId); void externalGameEventContainerReceived(const GameEventContainer &cont, qint64 sessionId); void externalResponseReceived(const Response &resp, qint64 sessionId); - + virtual void doSendIslMessage(const IslMessage & /* msg */, int /* serverId */) { } protected: void prepareDestroy(); @@ -113,7 +114,7 @@ protected: QMap externalUsers; QMap rooms; QMap databaseInterfaces; - + int getUsersCount() const; int getGamesCount() const; void addRoom(Server_Room *newRoom); diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 7df87189..ef1150f7 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -45,18 +45,18 @@ void Server_ProtocolHandler::prepareDestroy() if (deleted) return; deleted = true; - + QMapIterator roomIterator(rooms); while (roomIterator.hasNext()) roomIterator.next().value()->removeClient(this); - + QMap > tempGames(getGames()); - + server->roomsLock.lockForRead(); QMapIterator > gameIterator(tempGames); while (gameIterator.hasNext()) { gameIterator.next(); - + Server_Room *r = server->getRooms().value(gameIterator.value().first); if (!r) continue; @@ -73,16 +73,16 @@ void Server_ProtocolHandler::prepareDestroy() r->gamesLock.unlock(); continue; } - + p->disconnectClient(); - + g->gameMutex.unlock(); r->gamesLock.unlock(); } server->roomsLock.unlock(); - + server->removeClient(this); - + deleteLater(); } @@ -91,7 +91,7 @@ void Server_ProtocolHandler::sendProtocolItem(const Response &item) ServerMessage msg; msg.mutable_response()->CopyFrom(item); msg.set_message_type(ServerMessage::RESPONSE); - + transmitProtocolItem(msg); } @@ -100,7 +100,7 @@ void Server_ProtocolHandler::sendProtocolItem(const SessionEvent &item) ServerMessage msg; msg.mutable_session_event()->CopyFrom(item); msg.set_message_type(ServerMessage::SESSION_EVENT); - + transmitProtocolItem(msg); } @@ -109,7 +109,7 @@ void Server_ProtocolHandler::sendProtocolItem(const GameEventContainer &item) ServerMessage msg; msg.mutable_game_event_container()->CopyFrom(item); msg.set_message_type(ServerMessage::GAME_EVENT_CONTAINER); - + transmitProtocolItem(msg); } @@ -118,7 +118,7 @@ void Server_ProtocolHandler::sendProtocolItem(const RoomEvent &item) ServerMessage msg; msg.mutable_room_event()->CopyFrom(item); msg.set_message_type(ServerMessage::ROOM_EVENT); - + transmitProtocolItem(msg); } @@ -167,7 +167,7 @@ Response::ResponseCode Server_ProtocolHandler::processRoomCommandContainer(const Server_Room *room = rooms.value(cont.room_id(), 0); if (!room) return Response::RespNotInRoom; - + Response::ResponseCode finalResponseCode = Response::RespOk; for (int i = cont.room_command_size() - 1; i >= 0; --i) { Response::ResponseCode resp = Response::RespInvalidCommand; @@ -206,17 +206,17 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + QMap > gameMap = getGames(); if (!gameMap.contains(cont.game_id())) return Response::RespNotInRoom; const QPair roomIdAndPlayerId = gameMap.value(cont.game_id()); - + QReadLocker roomsLocker(&server->roomsLock); Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first); if (!room) return Response::RespNotInRoom; - + QReadLocker roomGamesLocker(&room->gamesLock); Server_Game *game = room->getGames().value(cont.game_id()); if (!game) { @@ -231,12 +231,12 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const } return Response::RespNotInRoom; } - + QMutexLocker gameLocker(&game->gameMutex); Server_Player *player = game->getPlayers().value(roomIdAndPlayerId.second); if (!player) return Response::RespNotInRoom; - + int commandCountingInterval = server->getCommandCountingInterval(); int maxCommandCountPerInterval = server->getMaxCommandCountPerInterval(); GameEventStorage ges; @@ -255,7 +255,7 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const for (int i = 0; i < commandCountOverTime.size(); ++i) totalCount += commandCountOverTime[i]; - + if (totalCount > maxCommandCountPerInterval) return Response::RespChatFlood; } @@ -266,7 +266,7 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const finalResponseCode = resp; } ges.sendToGame(game); - + return finalResponseCode; } @@ -283,7 +283,7 @@ Response::ResponseCode Server_ProtocolHandler::processModeratorCommandContainer( const ModeratorCommand &sc = cont.moderator_command(i); const int num = getPbExtension(sc); logDebugMessage(QString::fromStdString(sc.ShortDebugString())); - + resp = processExtendedModeratorCommand(num, sc, rc); if (resp != Response::RespOk) finalResponseCode = resp; @@ -304,7 +304,7 @@ Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(cons const AdminCommand &sc = cont.admin_command(i); const int num = getPbExtension(sc); logDebugMessage(QString::fromStdString(sc.ShortDebugString())); - + resp = processExtendedAdminCommand(num, sc, rc); if (resp != Response::RespOk) finalResponseCode = resp; @@ -317,12 +317,12 @@ void Server_ProtocolHandler::processCommandContainer(const CommandContainer &con // Command processing must be disabled after prepareDestroy() has been called. if (deleted) return; - + lastDataReceived = timeRunning; - + ResponseContainer responseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1); Response::ResponseCode finalResponseCode; - + if (cont.game_command_size()) finalResponseCode = processGameCommandContainer(cont, responseContainer); else if (cont.room_command_size()) @@ -335,28 +335,37 @@ void Server_ProtocolHandler::processCommandContainer(const CommandContainer &con finalResponseCode = processAdminCommandContainer(cont, responseContainer); else finalResponseCode = Response::RespInvalidCommand; - + if ((finalResponseCode != Response::RespNothing)) sendResponseContainer(responseContainer, finalResponseCode); } void Server_ProtocolHandler::pingClockTimeout() { + + int cmdcountinterval = server->getCommandCountingInterval(); + int msgcountinterval = server->getMessageCountingInterval(); + int pingclockinterval = server->getPingClockInterval(); + int interval = server->getMessageCountingInterval(); if (interval > 0) { - messageSizeOverTime.prepend(0); - if (messageSizeOverTime.size() > server->getMessageCountingInterval()) - messageSizeOverTime.removeLast(); - messageCountOverTime.prepend(0); - if (messageCountOverTime.size() > server->getMessageCountingInterval()) - messageCountOverTime.removeLast(); + if(pingclockinterval > 0) { + messageSizeOverTime.prepend(0); + if (messageSizeOverTime.size() > (msgcountinterval / pingclockinterval)) + messageSizeOverTime.removeLast(); + messageCountOverTime.prepend(0); + if (messageCountOverTime.size() > (msgcountinterval / pingclockinterval)) + messageCountOverTime.removeLast(); + } } interval = server->getCommandCountingInterval(); if (interval > 0) { - commandCountOverTime.prepend(0); - if (commandCountOverTime.size() > server->getCommandCountingInterval()) - commandCountOverTime.removeLast(); + if (pingclockinterval > 0) { + commandCountOverTime.prepend(0); + if (commandCountOverTime.size() > (cmdcountinterval / pingclockinterval)) + commandCountOverTime.removeLast(); + } } if (timeRunning - lastDataReceived > server->getMaxPlayerInactivityTime()) @@ -398,27 +407,27 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd case UserIsInactive: return Response::RespAccountNotActivated; default: authState = res; } - + userName = QString::fromStdString(userInfo->name()); Event_ServerMessage event; event.set_message(server->getLoginMessage().toStdString()); rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); - + Response_Login *re = new Response_Login; re->mutable_user_info()->CopyFrom(copyUserInfo(true)); - + if (authState == PasswordRight) { QMapIterator buddyIterator(databaseInterface->getBuddyList(userName)); while (buddyIterator.hasNext()) re->add_buddy_list()->CopyFrom(buddyIterator.next().value()); - + QMapIterator ignoreIterator(databaseInterface->getIgnoreList(userName)); while (ignoreIterator.hasNext()) re->add_ignore_list()->CopyFrom(ignoreIterator.next().value()); } - + joinPersistentGames(rc); - + rc.setResponseExtension(re); return Response::RespOk; } @@ -427,21 +436,21 @@ Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message { if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + QReadLocker locker(&server->clientsLock); - + QString receiver = QString::fromStdString(cmd.user_name()); Server_AbstractUserInterface *userInterface = server->findUser(receiver); if (!userInterface) return Response::RespNameNotFound; if (databaseInterface->isInIgnoreList(receiver, QString::fromStdString(userInfo->name()))) return Response::RespInIgnoreList; - + Event_UserMessage event; event.set_sender_name(userInfo->name()); event.set_receiver_name(cmd.user_name()); event.set_message(cmd.message()); - + SessionEvent *se = prepareSessionEvent(event); userInterface->sendProtocolItem(*se); rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, se); @@ -455,10 +464,10 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_G { if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + // We don't need to check whether the user is logged in; persistent games should also work. // The client needs to deal with an empty result list. - + Response_GetGamesOfUser *re = new Response_GetGamesOfUser; server->roomsLock.lockForRead(); QMapIterator roomIterator(server->getRooms()); @@ -472,7 +481,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_G room->gamesLock.unlock(); } server->roomsLock.unlock(); - + rc.setResponseExtension(re); return Response::RespOk; } @@ -481,22 +490,22 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetUserInfo(const Command_GetU { if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + QString userName = QString::fromStdString(cmd.user_name()); Response_GetUserInfo *re = new Response_GetUserInfo; if (userName.isEmpty()) re->mutable_user_info()->CopyFrom(*userInfo); else { - + QReadLocker locker(&server->clientsLock); - + ServerInfo_User_Container *infoSource = server->findUser(userName); if (!infoSource) return Response::RespNameNotFound; - + re->mutable_user_info()->CopyFrom(infoSource->copyUserInfo(true, false, userInfo->user_level() & ServerInfo_User::IsModerator)); } - + rc.setResponseExtension(re); return Response::RespOk; } @@ -505,13 +514,13 @@ Response::ResponseCode Server_ProtocolHandler::cmdListRooms(const Command_ListRo { if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + Event_ListRooms event; QMapIterator roomIterator(server->getRooms()); while (roomIterator.hasNext()) roomIterator.next().value()->getInfo(*event.add_room_list(), false); rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); - + acceptsRoomListChanges = true; return Response::RespOk; } @@ -520,25 +529,25 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoo { if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + if (rooms.contains(cmd.room_id())) return Response::RespContextError; - + QReadLocker serverLocker(&server->roomsLock); Server_Room *r = server->getRooms().value(cmd.room_id(), 0); if (!r) return Response::RespNameNotFound; - + r->addClient(this); rooms.insert(r->getId(), r); - + Event_RoomSay joinMessageEvent; joinMessageEvent.set_message(r->getJoinMessage().toStdString()); rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(joinMessageEvent)); - + Response_JoinRoom *re = new Response_JoinRoom; r->getInfo(*re->mutable_room_info(), true); - + rc.setResponseExtension(re); return Response::RespOk; } @@ -547,7 +556,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdListUsers(const Command_ListUs { if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + Response_ListUsers *re = new Response_ListUsers; server->clientsLock.lockForRead(); QMapIterator userIterator = server->getUsers(); @@ -556,10 +565,10 @@ Response::ResponseCode Server_ProtocolHandler::cmdListUsers(const Command_ListUs QMapIterator extIterator = server->getExternalUsers(); while (extIterator.hasNext()) re->add_user_list()->CopyFrom(extIterator.next().value()->copyUserInfo(false)); - + acceptsUserListChanges = true; server->clientsLock.unlock(); - + rc.setResponseExtension(re); return Response::RespOk; } @@ -574,7 +583,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLeaveRoom(const Command_LeaveR Response::ResponseCode Server_ProtocolHandler::cmdRoomSay(const Command_RoomSay &cmd, Server_Room *room, ResponseContainer & /*rc*/) { QString msg = QString::fromStdString(cmd.message()); - + if (server->getMessageCountingInterval() > 0) { int totalSize = 0, totalCount = 0; if (messageSizeOverTime.isEmpty()) @@ -582,18 +591,18 @@ Response::ResponseCode Server_ProtocolHandler::cmdRoomSay(const Command_RoomSay messageSizeOverTime[0] += msg.size(); for (int i = 0; i < messageSizeOverTime.size(); ++i) totalSize += messageSizeOverTime[i]; - + if (messageCountOverTime.isEmpty()) messageCountOverTime.prepend(0); ++messageCountOverTime[0]; for (int i = 0; i < messageCountOverTime.size(); ++i) totalCount += messageCountOverTime[i]; - + if ((totalSize > server->getMaxMessageSizePerInterval()) || (totalCount > server->getMaxMessageCountPerInterval())) return Response::RespChatFlood; } msg.replace(QChar('\n'), QChar(' ')); - + room->say(QString::fromStdString(userInfo->name()), msg); databaseInterface->logMessage(userInfo->id(), QString::fromStdString(userInfo->name()), QString::fromStdString(userInfo->address()), msg, Server_DatabaseInterface::MessageTargetRoom, room->getId(), room->getName()); @@ -608,23 +617,23 @@ Response::ResponseCode Server_ProtocolHandler::cmdCreateGame(const Command_Creat const int gameId = databaseInterface->getNextGameId(); if (gameId == -1) return Response::RespInternalError; - + if (server->getMaxGamesPerUser() > 0) if (room->getGamesCreatedByUser(QString::fromStdString(userInfo->name())) >= server->getMaxGamesPerUser()) return Response::RespContextError; - + QList gameTypes; for (int i = cmd.game_type_ids_size() - 1; i >= 0; --i) gameTypes.append(cmd.game_type_ids(i)); - + QString description = QString::fromStdString(cmd.description()); if (description.size() > 60) description = description.left(60); - + Server_Game *game = new Server_Game(copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), cmd.max_players(), gameTypes, cmd.only_buddies(), cmd.only_registered(), cmd.spectators_allowed(), cmd.spectators_need_password(), cmd.spectators_can_talk(), cmd.spectators_see_everything(), room); game->addPlayer(this, rc, false, false); room->addGame(game); - + return Response::RespOk; } @@ -632,6 +641,6 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinGame(const Command_JoinGam { if (authState == NotLoggedIn) return Response::RespLoginNeeded; - + return room->processJoinGameCommand(cmd, rc, this); } diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index ac0d6707..f0388863 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -38,6 +38,14 @@ logfile=server.log ; it won't be logged. Default is empty; example: "kittens,ponies,faires" logfilters="" +; Set the time interval in seconds that servatrice will use to communicate with each connected client +; to verify the client has not timed out. Defaults is 1 seconds +clientkeepalive=1 + +; Maximum time in seconds a player can stay inactive with there client not even responding to pings, before is +; considered disconnected; default is 15 +max_player_inactivity_time=15 + [authentication] @@ -83,8 +91,8 @@ allowpunctuationprefix=false ; Enable this feature? Default false. ;enabled=false -; Require users to provide an email address in order to register. Newly registered users will receive an -; activation token by email, and will be required to input back this token on cockatrice at the first login +; Require users to provide an email address in order to register. Newly registered users will receive an +; activation token by email, and will be required to input back this token on cockatrice at the first login ; to get their account activated. Default true. ;requireemail=true @@ -119,7 +127,7 @@ subject="Cockatrice server account activation token" ; Email body. You can use these tags here: %username %token ; They will be substituted with the actual values in the email -; +; body="Hi %username, thank our for registering on our Cockatrice server\r\nHere's the activation token you need to supply for activating your account:\r\n\r\n%token\r\n\r\nHappy gaming!" [database] @@ -178,10 +186,6 @@ roomlist\1\game_types\3\name="GameType3" [game] -; Maximum time in seconds a player can stay inactive, with his client hot even responding to pings, before is -; considered disconnected; default is 15 -max_player_inactivity_time=15 - ; Maximum time in seconds all players in a game can stay inactive before the game is automatically closed; ; default is 120 max_game_inactivity_time=120 diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index b88b4f47..d30a8aaa 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -161,13 +161,13 @@ bool Servatrice::initServer() authenticationMethod = AuthenticationNone; } - bool maxUserLimitEnabled = settingsCache->value("security/enable_max_user_limit", false).toBool(); - qDebug() << "Maximum user limit enabled: " << maxUserLimitEnabled; + bool maxUserLimitEnabled = settingsCache->value("security/enable_max_user_limit", false).toBool(); + qDebug() << "Maximum user limit enabled: " << maxUserLimitEnabled; - if (maxUserLimitEnabled){ - int maxUserLimit = settingsCache->value("security/max_users_total", 500).toInt(); - qDebug() << "Maximum user limit: " << maxUserLimit; - } + if (maxUserLimitEnabled){ + int maxUserLimit = settingsCache->value("security/max_users_total", 500).toInt(); + qDebug() << "Maximum user limit: " << maxUserLimit; + } bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); @@ -273,8 +273,8 @@ bool Servatrice::initServer() updateLoginMessage(); maxGameInactivityTime = settingsCache->value("game/max_game_inactivity_time", 120).toInt(); - maxPlayerInactivityTime = settingsCache->value("game/max_player_inactivity_time", 15).toInt(); - + maxPlayerInactivityTime = settingsCache->value("server/max_player_inactivity_time", 15).toInt(); + pingClockInterval = settingsCache->value("server/clientkeepalive", 1).toInt(); maxUsersPerAddress = settingsCache->value("security/max_users_per_address", 4).toInt(); messageCountingInterval = settingsCache->value("security/message_counting_interval", 10).toInt(); maxMessageCountPerInterval = settingsCache->value("security/max_message_count_per_interval", 15).toInt(); @@ -283,90 +283,90 @@ bool Servatrice::initServer() commandCountingInterval = settingsCache->value("game/command_counting_interval", 10).toInt(); maxCommandCountPerInterval = settingsCache->value("game/max_command_count_per_interval", 20).toInt(); - try { if (settingsCache->value("servernetwork/active", 0).toInt()) { - qDebug() << "Connecting to ISL network."; - const QString certFileName = settingsCache->value("servernetwork/ssl_cert").toString(); - const QString keyFileName = settingsCache->value("servernetwork/ssl_key").toString(); - qDebug() << "Loading certificate..."; - QFile certFile(certFileName); - if (!certFile.open(QIODevice::ReadOnly)) - throw QString("Error opening certificate file: %1").arg(certFileName); - QSslCertificate cert(&certFile); + try { if (settingsCache->value("servernetwork/active", 0).toInt()) { + qDebug() << "Connecting to ISL network."; + const QString certFileName = settingsCache->value("servernetwork/ssl_cert").toString(); + const QString keyFileName = settingsCache->value("servernetwork/ssl_key").toString(); + qDebug() << "Loading certificate..."; + QFile certFile(certFileName); + if (!certFile.open(QIODevice::ReadOnly)) + throw QString("Error opening certificate file: %1").arg(certFileName); + QSslCertificate cert(&certFile); #if QT_VERSION < 0x050000 - if (!cert.isValid()) - throw(QString("Invalid certificate.")); + if (!cert.isValid()) + throw(QString("Invalid certificate.")); #else - const QDateTime currentTime = QDateTime::currentDateTime(); - if(currentTime < cert.effectiveDate() || - currentTime > cert.expiryDate() || - cert.isBlacklisted()) - throw(QString("Invalid certificate.")); + const QDateTime currentTime = QDateTime::currentDateTime(); + if(currentTime < cert.effectiveDate() || + currentTime > cert.expiryDate() || + cert.isBlacklisted()) + throw(QString("Invalid certificate.")); #endif - qDebug() << "Loading private key..."; - QFile keyFile(keyFileName); - if (!keyFile.open(QIODevice::ReadOnly)) - throw QString("Error opening private key file: %1").arg(keyFileName); - QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); - if (key.isNull()) - throw QString("Invalid private key."); + qDebug() << "Loading private key..."; + QFile keyFile(keyFileName); + if (!keyFile.open(QIODevice::ReadOnly)) + throw QString("Error opening private key file: %1").arg(keyFileName); + QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + if (key.isNull()) + throw QString("Invalid private key."); - QMutableListIterator serverIterator(serverList); - while (serverIterator.hasNext()) { - const ServerProperties &prop = serverIterator.next(); - if (prop.cert == cert) { - serverIterator.remove(); - continue; - } + QMutableListIterator serverIterator(serverList); + while (serverIterator.hasNext()) { + const ServerProperties &prop = serverIterator.next(); + if (prop.cert == cert) { + serverIterator.remove(); + continue; + } - QThread *thread = new QThread; - thread->setObjectName("isl_" + QString::number(prop.id)); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + QThread *thread = new QThread; + thread->setObjectName("isl_" + QString::number(prop.id)); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - IslInterface *interface = new IslInterface(prop.id, prop.hostname, prop.address.toString(), prop.controlPort, prop.cert, cert, key, this); - interface->moveToThread(thread); - connect(interface, SIGNAL(destroyed()), thread, SLOT(quit())); + IslInterface *interface = new IslInterface(prop.id, prop.hostname, prop.address.toString(), prop.controlPort, prop.cert, cert, key, this); + interface->moveToThread(thread); + connect(interface, SIGNAL(destroyed()), thread, SLOT(quit())); - thread->start(); - QMetaObject::invokeMethod(interface, "initClient", Qt::BlockingQueuedConnection); - } + thread->start(); + QMetaObject::invokeMethod(interface, "initClient", Qt::BlockingQueuedConnection); + } - const int networkPort = settingsCache->value("servernetwork/port", 14747).toInt(); - qDebug() << "Starting ISL server on port" << networkPort; + const int networkPort = settingsCache->value("servernetwork/port", 14747).toInt(); + qDebug() << "Starting ISL server on port" << networkPort; - islServer = new Servatrice_IslServer(this, cert, key, this); - if (islServer->listen(QHostAddress::Any, networkPort)) - qDebug() << "ISL server listening."; - else - throw QString("islServer->listen()"); - } } catch (QString error) { - qDebug() << "ERROR --" << error; - return false; - } + islServer = new Servatrice_IslServer(this, cert, key, this); + if (islServer->listen(QHostAddress::Any, networkPort)) + qDebug() << "ISL server listening."; + else + throw QString("islServer->listen()"); + } } catch (QString error) { + qDebug() << "ERROR --" << error; + return false; + } - pingClock = new QTimer(this); - connect(pingClock, SIGNAL(timeout()), this, SIGNAL(pingClockTimeout())); - pingClock->start(1000); + pingClock = new QTimer(this); + connect(pingClock, SIGNAL(timeout()), this, SIGNAL(pingClockTimeout())); + pingClock->start(pingClockInterval * 1000); - int statusUpdateTime = settingsCache->value("server/statusupdate", 15000).toInt(); - statusUpdateClock = new QTimer(this); - connect(statusUpdateClock, SIGNAL(timeout()), this, SLOT(statusUpdate())); - if (statusUpdateTime != 0) { - qDebug() << "Starting status update clock, interval " << statusUpdateTime << " ms"; - statusUpdateClock->start(statusUpdateTime); - } + int statusUpdateTime = settingsCache->value("server/statusupdate", 15000).toInt(); + statusUpdateClock = new QTimer(this); + connect(statusUpdateClock, SIGNAL(timeout()), this, SLOT(statusUpdate())); + if (statusUpdateTime != 0) { + qDebug() << "Starting status update clock, interval " << statusUpdateTime << " ms"; + statusUpdateClock->start(statusUpdateTime); + } - const int numberPools = settingsCache->value("server/number_pools", 1).toInt(); - gameServer = new Servatrice_GameServer(this, numberPools, servatriceDatabaseInterface->getDatabase(), this); - gameServer->setMaxPendingConnections(1000); - const int gamePort = settingsCache->value("server/port", 4747).toInt(); - qDebug() << "Starting server on port" << gamePort; - if (gameServer->listen(QHostAddress::Any, gamePort)) - qDebug() << "Server listening."; - else { - qDebug() << "gameServer->listen(): Error:" << gameServer->errorString(); - return false; - } - return true; + const int numberPools = settingsCache->value("server/number_pools", 1).toInt(); + gameServer = new Servatrice_GameServer(this, numberPools, servatriceDatabaseInterface->getDatabase(), this); + gameServer->setMaxPendingConnections(1000); + const int gamePort = settingsCache->value("server/port", 4747).toInt(); + qDebug() << "Starting server on port" << gamePort; + if (gameServer->listen(QHostAddress::Any, gamePort)) + qDebug() << "Server listening."; + else { + qDebug() << "gameServer->listen(): Error:" << gameServer->errorString(); + return false; + } + return true; } void Servatrice::addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface) @@ -376,20 +376,20 @@ void Servatrice::addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterf void Servatrice::updateServerList() { - qDebug() << "Updating server list..."; + qDebug() << "Updating server list..."; - serverListMutex.lock(); - serverList.clear(); + serverListMutex.lock(); + serverList.clear(); - QSqlQuery *query = servatriceDatabaseInterface->prepareQuery("select id, ssl_cert, hostname, address, game_port, control_port from {prefix}_servers order by id asc"); - servatriceDatabaseInterface->execSqlQuery(query); - while (query->next()) { - ServerProperties prop(query->value(0).toInt(), QSslCertificate(query->value(1).toString().toUtf8()), query->value(2).toString(), QHostAddress(query->value(3).toString()), query->value(4).toInt(), query->value(5).toInt()); - serverList.append(prop); - qDebug() << QString("#%1 CERT=%2 NAME=%3 IP=%4:%5 CPORT=%6").arg(prop.id).arg(QString(prop.cert.digest().toHex())).arg(prop.hostname).arg(prop.address.toString()).arg(prop.gamePort).arg(prop.controlPort); - } + QSqlQuery *query = servatriceDatabaseInterface->prepareQuery("select id, ssl_cert, hostname, address, game_port, control_port from {prefix}_servers order by id asc"); + servatriceDatabaseInterface->execSqlQuery(query); + while (query->next()) { + ServerProperties prop(query->value(0).toInt(), QSslCertificate(query->value(1).toString().toUtf8()), query->value(2).toString(), QHostAddress(query->value(3).toString()), query->value(4).toInt(), query->value(5).toInt()); + serverList.append(prop); + qDebug() << QString("#%1 CERT=%2 NAME=%3 IP=%4:%5 CPORT=%6").arg(prop.id).arg(QString(prop.cert.digest().toHex())).arg(prop.hostname).arg(prop.address.toString()).arg(prop.gamePort).arg(prop.controlPort); + } - serverListMutex.unlock(); + serverListMutex.unlock(); } QList Servatrice::getServerList() const @@ -408,7 +408,7 @@ int Servatrice::getUsersWithAddress(const QHostAddress &address) const for (int i = 0; i < clients.size(); ++i) if (static_cast(clients[i])->getPeerAddress() == address) ++result; - + return result; } @@ -483,9 +483,9 @@ void Servatrice::statusUpdate() QSqlQuery *query = servatriceDatabaseInterface->prepareQuery("select a.name, b.email, b.token from {prefix}_activation_emails a left join {prefix}_users b on a.name = b.name"); if (!servatriceDatabaseInterface->execSqlQuery(query)) return; - + QSqlQuery *queryDelete = servatriceDatabaseInterface->prepareQuery("delete from {prefix}_activation_emails where name = :name"); - + while (query->next()) { const QString userName = query->value(0).toString(); const QString emailAddress = query->value(1).toString(); @@ -551,7 +551,7 @@ void Servatrice::shutdownTimeout() clients[i]->sendProtocolItem(*se); clientsLock.unlock(); delete se; - + if (!shutdownMinutes) deleteLater(); } diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 717e5ab8..a0421cd5 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -83,7 +83,7 @@ public: QHostAddress address; int gamePort; int controlPort; - + ServerProperties(int _id, const QSslCertificate &_cert, const QString &_hostname, const QHostAddress &_address, int _gamePort, int _controlPort) : id(_id), cert(_cert), hostname(_hostname), address(_address), gamePort(_gamePort), controlPort(_controlPort) { } }; @@ -115,17 +115,17 @@ private: QMutex txBytesMutex, rxBytesMutex; quint64 txBytes, rxBytes; int maxGameInactivityTime, maxPlayerInactivityTime; - int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser, commandCountingInterval, maxCommandCountPerInterval; + int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser, commandCountingInterval, maxCommandCountPerInterval, pingClockInterval; QString shutdownReason; int shutdownMinutes; QTimer *shutdownTimer; bool isFirstShutdownMessage; - + mutable QMutex serverListMutex; QList serverList; void updateServerList(); - + QMap islInterfaces; public slots: void scheduleShutdown(const QString &reason, int minutes); @@ -137,6 +137,7 @@ public: QString getServerName() const { return serverName; } QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; } bool getGameShouldPing() const { return true; } + int getPingClockInterval() const { return pingClockInterval; } int getMaxGameInactivityTime() const { return maxGameInactivityTime; } int getMaxPlayerInactivityTime() const { return maxPlayerInactivityTime; } int getMaxUsersPerAddress() const { return maxUsersPerAddress; } @@ -154,7 +155,7 @@ public: void incTxBytes(quint64 num); void incRxBytes(quint64 num); void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface); - + bool islConnectionExists(int serverId) const; void addIslInterface(int serverId, IslInterface *interface); void removeIslInterface(int serverId);