improved logging, improved server multithreading

This commit is contained in:
Max-Wilhelm Bruker 2011-03-22 17:55:20 +01:00
parent 8edc5b0635
commit fd7593edc1
11 changed files with 128 additions and 31 deletions

View file

@ -353,6 +353,7 @@ ResponseCode Server_ProtocolHandler::cmdJoinRoom(Command_JoinRoom *cmd, CommandC
return RespNameNotFound;
r->addClient(this);
connect(r, SIGNAL(gameCreated(Server_Game *)), this, SLOT(gameCreated(Server_Game *)));
rooms.insert(r->getId(), r);
enqueueProtocolItem(new Event_RoomSay(r->getId(), QString(), r->getJoinMessage()));
@ -420,13 +421,17 @@ ResponseCode Server_ProtocolHandler::cmdCreateGame(Command_CreateGame *cmd, Comm
for (int i = 0; i < gameTypeList.size(); ++i)
gameTypes.append(gameTypeList[i]->getData());
Server_Game *game = room->createGame(cmd->getDescription(), cmd->getPassword(), cmd->getMaxPlayers(), gameTypes, cmd->getOnlyBuddies(), cmd->getOnlyRegistered(), cmd->getSpectatorsAllowed(), cmd->getSpectatorsNeedPassword(), cmd->getSpectatorsCanTalk(), cmd->getSpectatorsSeeEverything(), this);
room->createGame(cmd->getDescription(), cmd->getPassword(), cmd->getMaxPlayers(), gameTypes, cmd->getOnlyBuddies(), cmd->getOnlyRegistered(), cmd->getSpectatorsAllowed(), cmd->getSpectatorsNeedPassword(), cmd->getSpectatorsCanTalk(), cmd->getSpectatorsSeeEverything(), this);
return RespOk;
}
void Server_ProtocolHandler::gameCreated(Server_Game *game)
{
Server_Player *creator = game->getPlayers().values().first();
games.insert(game->getGameId(), QPair<Server_Game *, Server_Player *>(game, creator));
enqueueProtocolItem(new Event_GameJoined(game->getGameId(), game->getDescription(), creator->getPlayerId(), false, game->getSpectatorsCanTalk(), game->getSpectatorsSeeEverything(), false));
enqueueProtocolItem(GameEventContainer::makeNew(new Event_GameStateChanged(game->getGameStarted(), game->getActivePlayer(), game->getActivePhase(), game->getGameState(creator)), game->getGameId()));
return RespOk;
sendProtocolItem(new Event_GameJoined(game->getGameId(), game->getDescription(), creator->getPlayerId(), false, game->getSpectatorsCanTalk(), game->getSpectatorsSeeEverything(), false));
sendProtocolItem(GameEventContainer::makeNew(new Event_GameStateChanged(game->getGameStarted(), game->getActivePlayer(), game->getActivePhase(), game->getGameState(creator)), game->getGameId()));
}
ResponseCode Server_ProtocolHandler::cmdJoinGame(Command_JoinGame *cmd, CommandContainer * /*cont*/, Server_Room *room)

View file

@ -91,6 +91,7 @@ private:
ResponseCode processCommandHelper(Command *command, CommandContainer *cont);
private slots:
void pingClockTimeout();
void gameCreated(Server_Game *game);
public:
Server_ProtocolHandler(Server *_server, QObject *parent = 0);
~Server_ProtocolHandler();

View file

@ -6,6 +6,7 @@
Server_Room::Server_Room(int _id, const QString &_name, const QString &_description, bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, Server *parent)
: QObject(parent), id(_id), name(_name), description(_description), autoJoin(_autoJoin), joinMessage(_joinMessage), gameTypes(_gameTypes)
{
connect(this, SIGNAL(sigCreateGame(const QString &, const QString &, int, const QList<int> &, bool, bool, bool, bool, bool, bool, Server_ProtocolHandler *)), this, SLOT(doCreateGame(const QString &, const QString &, int, const QList<int> &, bool, bool, bool, bool, bool, bool, Server_ProtocolHandler *)));
}
Server *Server_Room::getServer() const
@ -68,7 +69,7 @@ void Server_Room::broadcastGameListUpdate(Server_Game *game)
delete event;
}
Server_Game *Server_Room::createGame(const QString &description, const QString &password, int maxPlayers, const QList<int> &gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator)
void Server_Room::doCreateGame(const QString &description, const QString &password, int maxPlayers, const QList<int> &gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator)
{
Server_Game *newGame = new Server_Game(creator, static_cast<Server *>(parent())->getNextGameId(), description, password, maxPlayers, gameTypes, onlyBuddies, onlyRegistered, spectatorsAllowed, spectatorsNeedPassword, spectatorsCanTalk, spectatorsSeeEverything, this);
games.insert(newGame->getGameId(), newGame);
@ -78,8 +79,11 @@ Server_Game *Server_Room::createGame(const QString &description, const QString &
emit gameCreated(newGame);
emit roomInfoChanged();
}
return newGame;
void Server_Room::createGame(const QString &description, const QString &password, int maxPlayers, const QList<int> &gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator)
{
emit sigCreateGame(description, password, maxPlayers, gameTypes, onlyBuddies, onlyRegistered, spectatorsAllowed, spectatorsNeedPassword, spectatorsCanTalk, spectatorsSeeEverything, creator);
}
void Server_Room::removeGame()

View file

@ -19,6 +19,8 @@ signals:
void roomInfoChanged();
void gameCreated(Server_Game *game);
void gameClosing(int gameId);
void sigCreateGame(const QString &description, const QString &password, int maxPlayers, const QList<int> &_gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator);
private:
int id;
QString name;
@ -28,6 +30,7 @@ private:
QStringList gameTypes;
QMap<int, Server_Game *> games;
private slots:
void doCreateGame(const QString &description, const QString &password, int maxPlayers, const QList<int> &_gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator);
void removeGame();
public:
Server_Room(int _id, const QString &_name, const QString &_description, bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, Server *parent);
@ -44,7 +47,7 @@ public:
void removeClient(Server_ProtocolHandler *client);
void say(Server_ProtocolHandler *client, const QString &s);
void broadcastGameListUpdate(Server_Game *game);
Server_Game *createGame(const QString &description, const QString &password, int maxPlayers, const QList<int> &_gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator);
void createGame(const QString &description, const QString &password, int maxPlayers, const QList<int> &_gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator);
void sendRoomEvent(RoomEvent *event);
};

View file

@ -17,6 +17,7 @@ HEADERS += src/main.h \
src/servatrice.h \
src/serversocketinterface.h \
src/server_logger.h \
src/serversocketthread.h \
../common/color.h \
../common/serializable_item.h \
../common/decklist.h \
@ -40,6 +41,7 @@ SOURCES += src/main.cpp \
src/servatrice.cpp \
src/serversocketinterface.cpp \
src/server_logger.cpp \
src/serversocketthread.cpp \
../common/serializable_item.cpp \
../common/decklist.cpp \
../common/protocol.cpp \

View file

@ -21,6 +21,8 @@
#include <QCoreApplication>
#include <QTextCodec>
#include <iostream>
#include <QMetaType>
#include <QSettings>
#include "servatrice.h"
#include "server_logger.h"
#include "rng_sfmt.h"
@ -65,14 +67,27 @@ void testRNG()
std::cerr << std::endl << std::endl;
}
void myMessageOutput(QtMsgType /*type*/, const char *msg)
{
logger->logMessage(msg);
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
app.setOrganizationName("Cockatrice");
app.setApplicationName("Servatrice");
QStringList args = app.arguments();
bool testRandom = args.contains("--test-random");
qRegisterMetaType<QList<int> >("QList<int>");
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
logger = new ServerLogger;
QSettings *settings = new QSettings("servatrice.ini", QSettings::IniFormat);
logger = new ServerLogger(settings->value("server/logfile").toString());
qInstallMsgHandler(myMessageOutput);
#ifdef Q_OS_UNIX
struct sigaction hup;
hup.sa_handler = ServerLogger::hupSignalHandler;
@ -86,9 +101,10 @@ int main(int argc, char *argv[])
std::cerr << "Servatrice " << Servatrice::versionString.toStdString() << " starting." << std::endl;
std::cerr << "-------------------------" << std::endl;
testRNG();
if (testRandom)
testRNG();
Servatrice server;
Servatrice server(settings);
std::cerr << "-------------------------" << std::endl;
std::cerr << "Server initialized." << std::endl;
@ -96,6 +112,7 @@ int main(int argc, char *argv[])
int retval = app.exec();
delete rng;
delete settings;
return retval;
}

View file

@ -24,10 +24,18 @@
#include "servatrice.h"
#include "server_room.h"
#include "serversocketinterface.h"
#include "serversocketthread.h"
#include "protocol.h"
Servatrice::Servatrice(QObject *parent)
: Server(parent), uptime(0)
void Servatrice_TcpServer::incomingConnection(int socketDescriptor)
{
ServerSocketThread *sst = new ServerSocketThread(socketDescriptor, server, this);
connect(sst, SIGNAL(clientAdded(ServerSocketInterface *)), this, SIGNAL(clientAdded(ServerSocketInterface *)));
sst->start();
}
Servatrice::Servatrice(QSettings *_settings, QObject *parent)
: Server(parent), dbMutex(QMutex::Recursive), settings(_settings), uptime(0)
{
pingClock = new QTimer(this);
connect(pingClock, SIGNAL(timeout()), this, SIGNAL(pingClockTimeout()));
@ -38,7 +46,6 @@ Servatrice::Servatrice(QObject *parent)
banTimeoutClock->start(60000);
ProtocolItem::initializeHash();
settings = new QSettings("servatrice.ini", QSettings::IniFormat, this);
int statusUpdateTime = settings->value("server/statusupdate").toInt();
statusUpdateClock = new QTimer(this);
@ -48,8 +55,8 @@ Servatrice::Servatrice(QObject *parent)
statusUpdateClock->start(statusUpdateTime);
}
tcpServer = new QTcpServer(this);
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
tcpServer = new Servatrice_TcpServer(this);
connect(tcpServer, SIGNAL(clientAdded(ServerSocketInterface *)), this, SLOT(newConnection(ServerSocketInterface *)));
int port = settings->value("server/port", 4747).toInt();
qDebug() << "Starting server on port" << port;
tcpServer->listen(QHostAddress::Any, port);
@ -133,6 +140,7 @@ bool Servatrice::openDatabase()
void Servatrice::checkSql()
{
QMutexLocker locker(&dbMutex);
if (!QSqlDatabase::database().exec("select 1").isActive())
openDatabase();
}
@ -145,15 +153,14 @@ bool Servatrice::execSqlQuery(QSqlQuery &query)
return false;
}
void Servatrice::newConnection()
void Servatrice::newConnection(ServerSocketInterface *client)
{
QTcpSocket *socket = tcpServer->nextPendingConnection();
ServerSocketInterface *ssi = new ServerSocketInterface(this, socket);
addClient(ssi);
addClient(client);
}
AuthenticationResult Servatrice::checkUserPassword(const QString &user, const QString &password)
{
QMutexLocker locker(&dbMutex);
const QString method = settings->value("authentication/method").toString();
if (method == "none")
return UnknownUser;
@ -181,6 +188,7 @@ AuthenticationResult Servatrice::checkUserPassword(const QString &user, const QS
bool Servatrice::userExists(const QString &user)
{
QMutexLocker locker(&dbMutex);
const QString method = settings->value("authentication/method").toString();
if (method == "sql") {
checkSql();
@ -219,6 +227,7 @@ ServerInfo_User *Servatrice::evalUserQueryResult(const QSqlQuery &query, bool co
ServerInfo_User *Servatrice::getUserData(const QString &name)
{
QMutexLocker locker(&dbMutex);
const QString method = settings->value("authentication/method").toString();
if (method == "sql") {
checkSql();
@ -248,6 +257,7 @@ int Servatrice::getUsersWithAddress(const QHostAddress &address) const
QMap<QString, ServerInfo_User *> Servatrice::getBuddyList(const QString &name)
{
QMutexLocker locker(&dbMutex);
QMap<QString, ServerInfo_User *> result;
const QString method = settings->value("authentication/method").toString();
@ -270,6 +280,7 @@ QMap<QString, ServerInfo_User *> Servatrice::getBuddyList(const QString &name)
QMap<QString, ServerInfo_User *> Servatrice::getIgnoreList(const QString &name)
{
QMutexLocker locker(&dbMutex);
QMap<QString, ServerInfo_User *> result;
const QString method = settings->value("authentication/method").toString();
@ -318,6 +329,7 @@ void Servatrice::updateBanTimer()
void Servatrice::updateLoginMessage()
{
QMutexLocker locker(&dbMutex);
checkSql();
QSqlQuery query;
query.prepare("select message from " + dbPrefix + "_servermessages order by timest desc limit 1");
@ -336,6 +348,7 @@ void Servatrice::updateLoginMessage()
void Servatrice::statusUpdate()
{
QMutexLocker locker(&dbMutex);
uptime += statusUpdateClock->interval() / 1000;
checkSql();

View file

@ -21,6 +21,7 @@
#define SERVATRICE_H
#include <QTcpServer>
#include <QMutex>
#include "server.h"
class QSqlDatabase;
@ -28,16 +29,33 @@ class QSettings;
class QSqlQuery;
class QTimer;
class Servatrice;
class ServerSocketInterface;
class Servatrice_TcpServer : public QTcpServer {
Q_OBJECT
private:
Servatrice *server;
public:
Servatrice_TcpServer(Servatrice *_server, QObject *parent = 0)
: QTcpServer(parent), server(_server) { }
protected:
void incomingConnection(int socketDescriptor);
signals:
void clientAdded(ServerSocketInterface *client);
};
class Servatrice : public Server
{
Q_OBJECT
private slots:
void newConnection();
void newConnection(ServerSocketInterface *client);
void statusUpdate();
void updateBanTimer();
public:
QMutex dbMutex;
static const QString versionString;
Servatrice(QObject *parent = 0);
Servatrice(QSettings *_settings, QObject *parent = 0);
~Servatrice();
bool openDatabase();
void checkSql();

View file

@ -2,19 +2,27 @@
#include <QSocketNotifier>
#include <QFile>
#include <QTextStream>
#include <QMutex>
#include <QDateTime>
#include <QThread>
#ifdef Q_OS_UNIX
#include <sys/types.h>
#include <sys/socket.h>
#endif
ServerLogger::ServerLogger(QObject *parent)
ServerLogger::ServerLogger(const QString &logFileName, QObject *parent)
: QObject(parent)
{
logFile = new QFile("server.log", this);
logFile->open(QIODevice::Append);
if (!logFileName.isEmpty()) {
logFile = new QFile(logFileName, this);
logFile->open(QIODevice::Append);
#ifdef Q_OS_UNIX
::socketpair(AF_UNIX, SOCK_STREAM, 0, sigHupFD);
::socketpair(AF_UNIX, SOCK_STREAM, 0, sigHupFD);
#endif
snHup = new QSocketNotifier(sigHupFD[1], QSocketNotifier::Read, this);
connect(snHup, SIGNAL(activated(int)), this, SLOT(handleSigHup()));
snHup = new QSocketNotifier(sigHupFD[1], QSocketNotifier::Read, this);
connect(snHup, SIGNAL(activated(int)), this, SLOT(handleSigHup()));
} else
logFile = 0;
}
ServerLogger::~ServerLogger()
@ -23,18 +31,30 @@ ServerLogger::~ServerLogger()
void ServerLogger::logMessage(QString message)
{
if (!logFile)
return;
static QMutex mutex;
mutex.lock();
QTextStream stream(logFile);
stream << message << "\n";
stream << QDateTime::currentDateTime().toString() << " " << ((void *) QThread::currentThread()) << " " << message << "\n";
mutex.unlock();
}
void ServerLogger::hupSignalHandler(int /*unused*/)
{
if (!logFile)
return;
char a = 1;
::write(sigHupFD[0], &a, sizeof(a));
}
void ServerLogger::handleSigHup()
{
if (!logFile)
return;
snHup->setEnabled(false);
char tmp;
::read(sigHupFD[1], &tmp, sizeof(tmp));

View file

@ -9,7 +9,7 @@ class QFile;
class ServerLogger : public QObject {
Q_OBJECT
public:
ServerLogger(QObject *parent = 0);
ServerLogger(const QString &logFileName, QObject *parent = 0);
~ServerLogger();
static void hupSignalHandler(int unused);
public slots:

View file

@ -101,14 +101,18 @@ void ServerSocketInterface::catchSocketError(QAbstractSocket::SocketError socket
void ServerSocketInterface::sendProtocolItem(ProtocolItem *item, bool deleteItem)
{
static QMutex mutex;
mutex.lock();
item->write(xmlWriter);
socket->flush();
mutex.unlock();
if (deleteItem)
delete item;
}
int ServerSocketInterface::getUserIdInDB(const QString &name) const
{
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("select id from " + servatrice->getDbPrefix() + "_users where name = :name");
query.bindValue(":name", name);
@ -142,6 +146,7 @@ ResponseCode ServerSocketInterface::cmdAddToList(Command_AddToList *cmd, Command
if (id1 == id2)
return RespContextError;
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("insert into " + servatrice->getDbPrefix() + "_" + list + "list (id_user1, id_user2) values(:id1, :id2)");
query.bindValue(":id1", id1);
@ -180,6 +185,7 @@ ResponseCode ServerSocketInterface::cmdRemoveFromList(Command_RemoveFromList *cm
if (id2 < 0)
return RespNameNotFound;
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("delete from " + servatrice->getDbPrefix() + "_" + list + "list where id_user1 = :id1 and id_user2 = :id2");
query.bindValue(":id1", id1);
@ -206,6 +212,7 @@ int ServerSocketInterface::getDeckPathId(int basePathId, QStringList path)
if (path[0].isEmpty())
return 0;
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and name = :name and user = :user");
query.bindValue(":id_parent", basePathId);
@ -229,6 +236,7 @@ int ServerSocketInterface::getDeckPathId(const QString &path)
bool ServerSocketInterface::deckListHelper(DeckList_Directory *folder)
{
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("select id, name from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and user = :user");
query.bindValue(":id_parent", folder->getId());
@ -288,6 +296,7 @@ ResponseCode ServerSocketInterface::cmdDeckNewDir(Command_DeckNewDir *cmd, Comma
if (folderId == -1)
return RespNameNotFound;
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_folders (id_parent, user, name) values(:id_parent, :user, :name)");
query.bindValue(":id_parent", folderId);
@ -302,6 +311,7 @@ void ServerSocketInterface::deckDelDirHelper(int basePathId)
{
servatrice->checkSql();
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent");
@ -340,6 +350,7 @@ ResponseCode ServerSocketInterface::cmdDeckDel(Command_DeckDel *cmd, CommandCont
servatrice->checkSql();
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and user = :user");
@ -379,6 +390,7 @@ ResponseCode ServerSocketInterface::cmdDeckUpload(Command_DeckUpload *cmd, Comma
if (deckName.isEmpty())
deckName = "Unnamed deck";
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_files (id_folder, user, name, upload_time, content) values(:id_folder, :user, :name, NOW(), :content)");
query.bindValue(":id_folder", folderId);
@ -395,6 +407,7 @@ DeckList *ServerSocketInterface::getDeckFromDatabase(int deckId)
{
servatrice->checkSql();
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("select content from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and user = :user");
@ -447,6 +460,7 @@ ResponseCode ServerSocketInterface::cmdBanFromServer(Command_BanFromServer *cmd,
if (user->getUserInfo()->getUserLevel() & ServerInfo_User::IsRegistered) {
// Registered users can be banned by name.
if (minutes == 0) {
QMutexLocker locker(&servatrice->dbMutex);
QSqlQuery query;
query.prepare("update " + servatrice->getDbPrefix() + "_users set banned=1 where name = :name");
query.bindValue(":name", userName);