/*
# Copyright (c) 2009,2010 - OpenSLX Project, Computer Center University of
# Freiburg
#
# This program is free software distributed under the GPL version 2.
# See http://openslx.org/COPYING
#
# If you have any feedback please consult http://openslx.org/feedback and
# send your suggestions, praise, or complaints to feedback@openslx.org
#
# General information about OpenSLX can be found at http://openslx.org/
# -----------------------------------------------------------------------------
# src/net/pvsListenServer.cpp
# - handling of clients (server side)
# - accepting new connections etc.
# -----------------------------------------------------------------------------
*/
#include "pvsListenServer.h"
#include "pvsMsg.h"
#include "pvsClientConnection.h"
#include "src/util/consoleLogger.h"
#include <QtNetwork/QSslSocket>
#include <QBuffer>
#include <QByteArray>
#include <QSettings>
#include "SslServer.h"
#include <cassert>
//#define verbose
#include "mcast/McastConfiguration.h"
#include <QTime>
// Create listener
PVSListenServer::PVSListenServer(int port, int clients)
{
_id = generateID();
_listenSocket = NULL;
_timer = 0;
if (clients < 1)
_clientsMax = 100;
else
_clientsMax = clients;
_port = port;
_mcastConfig = 0;
init();
}
PVSListenServer::~PVSListenServer()
{
if (_listenSocket != NULL)
{
disconnect(_listenSocket, SIGNAL(newConnection()), this, SLOT(
server_connectionRequest()));
_listenSocket->deleteLater();
}
}
PVSClientConnection* PVSListenServer::getConnectionFromId(int id)
{
if (!(_clients.empty()))
{
// Check for ping timeout
for (std::list<PVSClientConnection*>::iterator it = _clients.begin(); it
!= _clients.end(); it++)
{
PVSClientConnection *client = *it;
if (client)
if (client->getID() == id)
return client;
}
}
return NULL;
}
void PVSListenServer::sendToAll(PVSMsg msg)
{
if (!(_clients.empty()))
{
// Check for ping timeout
for (std::list<PVSClientConnection*>::iterator it = _clients.begin(); it
!= _clients.end(); it++)
{
PVSClientConnection *client = *it;
if (client)
client->push_back_send(msg);
}
}
}
// listen on a specific port
bool PVSListenServer::startListen(int port)
{
if (_listenSocket != NULL)
{
ConsoleLog writeError(
QString("Server already listening on port :").append(
int2String(_port)));
if (shutdown())
{
ConsoleLog writeError(QString("Server shutdown failed!"));
return false;
}
}
_port = port;
return init();
}
// stop listening and free socket descriptor
bool PVSListenServer::shutdown()
{
if (_timer != 0)
killTimer( _timer);
_timer = 0;
if (_listenSocket != NULL)
{
disconnect(_listenSocket, SIGNAL(newConnection()), this, SLOT(
server_connectionRequest()));
_listenSocket->close();
_listenSocket->deleteLater();
}
_listenSocket = NULL;
_fresh = true;
return false;
}
// timer event, CLIENT_KEEPALIVE interval
void PVSListenServer::timerEvent(QTimerEvent *event)
{
if (!(_clients.empty()))
{
// Check for ping timeout
time_t refval = time(NULL) - (CLIENT_TIMEOUT/1000);
for (std::list<PVSClientConnection*>::iterator it = _clients.begin(); it
!= _clients.end(); it++)
{
PVSClientConnection *client = *it;
client->ping();
if (!client->isConnected())
{ // disconnected
if (disconnectClient(client))
break; // list was modified, iterator not valid anymore
}
if (client->lastData() < refval)
{ // ping timeout
qDebug("DEBUG: Ping timeout for client %s", qPrintable(client->getNameUser()));
qDebug("DEBUG: Timeout was %i last response was %i", refval, client->lastData());
if (disconnectClient(client))
break; // list was modified, iterator not valid anymore
}
}
}
}
// remove a client
bool PVSListenServer::disconnectClient(PVSClientConnection* delinquent)
{
assert(delinquent);
delinquent->deleteLater();
if (!(_clients.empty()))
{
for (std::list<PVSClientConnection*>::iterator it = _clients.begin(); it
!= _clients.end(); it++)
{
if ((*it) == delinquent)
{
(*it)->closeConnection();
_clients.remove((*it));
onClientDisconnected(delinquent);
return true;
}
}
}
return false;
}
// All sorts of events:
void PVSListenServer::onConnectionRemoved(PVSClientConnection* delinquent)
{
}
void PVSListenServer::onClientConnected(PVSClientConnection* connected)
{
connected->setServerID(_id);
connected->setID(generateID());
connected->push_back_send(mcastConfigMessage());
}
void PVSListenServer::onClientDisconnected(PVSClientConnection* disconnected)
{
//
}
// Get client class for a given socket
PVSClientConnection* PVSListenServer::getConnectionFromSocket(QSslSocket* sock)
{
for (std::list<PVSClientConnection*>::iterator it = _clients.begin(); it
!= _clients.end(); it++)
{
if ((*it)->getSocket() == sock) // match!
return (*it);
}
return NULL;
}
// generate a random (unique) id
unsigned int PVSListenServer::generateID()
{
int random_integer;
do
{
random_integer = getRandom(0, 0);
}
while (getConnectionFromID(random_integer));
return (unsigned int) random_integer;
}
// Get client class for a given id
PVSClientConnection* PVSListenServer::getConnectionFromID(int id)
{
for (std::list<PVSClientConnection*>::iterator it = _clients.begin(); it
!= _clients.end(); it++)
{
if ((*it)->getID() == id || id == _id)
{
return (*it);
}
}
return NULL;
}
// Initialize listening socket
bool PVSListenServer::init()
{
if (_mcastConfig)
delete _mcastConfig;
_mcastConfig = new McastConfiguration(this);
loadMcastConfig();
if (_listenSocket != NULL)
shutdown();
if (_port > 0)
{
// make a socket, bind it, and listen on it:
_listenSocket = new SslServer;
QHostAddress addr(QHostAddress::Any);
_listenSocket->listen(addr, _port);
if (!_listenSocket->isListening())
{
ConsoleLog writeNetwork(
QString("Error listening on Port ").append(int2String(
_port)));
return false;
}
ConsoleLog writeNetwork(
QString("Server is listening. Socket: ").append(ptr2String(
_listenSocket)));
connect(_listenSocket, SIGNAL(newConnection()), this, SLOT(
server_connectionRequest()));
_fresh = true;
if (_timer != 0)
killTimer( _timer);
_timer = startTimer(CLIENT_KEEPALIVE);
return true;
}
return false;
}
void PVSListenServer::server_connectionRequest()
{
//printf("ConnReq\n");
if (_listenSocket == NULL)
return; // o.O
while (_listenSocket->hasPendingConnections())
{
QSslSocket* sock = (QSslSocket*)_listenSocket->nextPendingConnection();
//printf("Got one: %d: %s\n", (int)sock->isEncrypted(), sock->peerAddress().toString().toUtf8().data());
if (_clients.size() >= _clientsMax)
{
sock->abort();
sock->deleteLater();
continue;
}
PVSClientConnection* client = new PVSClientConnection(this, sock);
_clients.push_back(client); // create new client class and add to list
onClientConnected(client); // trigger event
_fresh = false;
}
}
void PVSListenServer::handleClientMsg(unsigned int clientID, PVSMsg msg)
{
qDebug("Got Message for client %ud: [%c][%s][%s]", clientID,
(char) msg.getType(), qPrintable(msg.getIdent()), qPrintable(msg.getMessage()));
msg.setSndID(clientID);
if (msg.getType() == PVSCOMMAND)
_commandDispatcher.fire(msg.getIdent(), msg);
else if (msg.getType() == PVSMESSAGE)
_chatDispatcher.fire(msg.getIdent(), msg);
else if (msg.getType() == PVSLOGIN)
_loginDispatcher.fire(msg.getIdent(), msg);
else
ConsoleLog
writeNetwork(
QString(
"Could not dispatch Message of unknown Type.\n\t Message was: ").append(
msg.getIdent().append(QString(":").append(
msg.getMessage()))));
}
bool PVSListenServer::isListening()
{
return _listenSocket != NULL && _listenSocket->isListening();
}
void PVSListenServer::loadMcastConfig()
{
QSettings settings;
_mcastConfig->loadFrom(&settings, "multicast-filetransfer");
}
void PVSListenServer::saveMcastConfig()
{
QSettings settings;
_mcastConfig->writeTo(&settings, "multicast-filetransfer");
settings.sync();
}
PVSMsg PVSListenServer::mcastConfigMessage()
{
// If anything is changed here, do not forget to
// 1. assign a new version number
// 2. adapt PVS::onCommand(PVSMsg) in pvs.cpp
QByteArray ba;
QDataStream strm(&ba, QIODevice::WriteOnly);
strm << (quint16)1 // version
<< _mcastConfig->multicastAddress()
<< _mcastConfig->multicastUDPPortBase()
<< _mcastConfig->multicastDPort()
<< _mcastConfig->multicastSPort()
<< _mcastConfig->multicastMTU()
<< _mcastConfig->multicastWinSize()
<< _mcastConfig->multicastRate()
<< _mcastConfig->multicastUseUDP();
QByteArray b64 = ba.toBase64();
QString message = QString::fromAscii(b64.constData(), b64.length());
PVSMsg msg(PVSCOMMAND, "MCASTFTCONFIG", message);
return msg;
}
void PVSListenServer::multicastReconfigure(McastConfiguration const* source)
{
_mcastConfig->multicastAddress(source->multicastAddress());
*_mcastConfig = *source;
saveMcastConfig();
sendToAll(mcastConfigMessage());
}
McastConfiguration const* PVSListenServer::getMulticastConfiguration()
{
return _mcastConfig;
}