2019-04-26 08:47:46 +03:00
# include "TcpServer.h"
# include <QCoreApplication>
# include <QtNetwork>
TcpServerError : : ~ TcpServerError ( ) { } // for vtable
TcpServer : : TcpServer ( const QHostAddress & a , quint16 p )
2019-04-28 11:06:50 +03:00
: QTcpServer ( nullptr ) , IdMixin ( newId ( ) ) , addr ( a ) , port ( p )
2019-04-26 08:47:46 +03:00
{
_thread . setObjectName ( prettyName ( ) ) ;
2019-04-28 11:06:50 +03:00
setObjectName ( prettyName ( ) ) ;
2019-04-26 08:47:46 +03:00
}
TcpServer : : ~ TcpServer ( )
{
Debug ( ) < < __FUNCTION__ ;
stop ( ) ;
}
QString TcpServer : : hostPort ( ) const
{
return QString ( " %1:%2 " ) . arg ( addr . toString ( ) ) . arg ( port ) ;
}
QString TcpServer : : prettyName ( ) const
{
2019-04-28 11:06:50 +03:00
return QString ( " Srv %1 (id: %2) " ).arg(hostPort()).arg(id) ;
2019-04-26 08:47:46 +03:00
}
void TcpServer : : tryStart ( )
{
if ( ! _thread . isRunning ( ) ) {
2019-04-28 11:59:14 +03:00
ThreadObjectMixin : : start ( ) ; // call super
2019-04-28 11:06:50 +03:00
Log ( ) < < " Starting listener service for " < < prettyName ( ) < < " ... " ;
2019-04-28 11:59:14 +03:00
if ( auto result = chan . get < QString > ( ) ; result ! = " ok " ) {
2019-04-26 08:47:46 +03:00
result = result . isEmpty ( ) ? " Startup timed out! " : result ;
throw TcpServerError ( result ) ;
}
Log ( ) < < " Service started, listening for connections on " < < hostPort ( ) ;
} else {
throw TcpServerError ( prettyName ( ) + " already started " ) ;
}
}
void TcpServer : : on_started ( )
{
QString result = " ok " ;
connect ( this , SIGNAL ( newConnection ( ) ) , this , SLOT ( on_newConnection ( ) ) ) ;
if ( ! listen ( addr , port ) ) {
result = errorString ( ) ;
result = result . isEmpty ( ) ? " Error binding/listening for connections " : result ;
Debug ( ) < < __FUNCTION__ < < " listen failed " ;
} else {
Debug ( ) < < " started ok " ;
}
chan . put ( result ) ;
}
void TcpServer : : on_finished ( )
{
disconnect ( this , SIGNAL ( newConnection ( ) ) , this , SLOT ( on_newConnection ( ) ) ) ;
close ( ) ; /// stop listening
chan . put ( " finished " ) ;
Debug ( ) < < __FUNCTION__ < < " finished. " ;
2019-04-28 11:59:14 +03:00
ThreadObjectMixin : : on_finished ( ) ;
2019-04-26 08:47:46 +03:00
}
static inline QString prettySock ( QAbstractSocket * sock )
{
return QString ( " %1:%2 " )
. arg ( sock ? sock - > peerAddress ( ) . toString ( ) : " (null) " )
. arg ( sock ? sock - > peerPort ( ) : 0 ) ;
}
void TcpServer : : on_newConnection ( )
{
QTcpSocket * sock = nextPendingConnection ( ) ;
if ( sock ) {
Debug ( ) < < " Got connection from: " < < prettySock ( sock ) ;
2019-04-28 14:59:01 +03:00
newClient ( sock ) ;
2019-04-26 08:47:46 +03:00
} else {
Warning ( ) < < __FUNCTION__ < < " : nextPendingConnection returned a nullptr! Called at the wrong time? FIXME! " ;
}
}
2019-04-27 22:43:09 +03:00
Client *
TcpServer : : newClient ( QTcpSocket * sock )
{
const auto id = newId ( ) ;
auto ret = clientsById [ id ] = new Client ( id , this , sock ) ;
// if deleted, we need to purge it from map
auto on_destroyed = [ id , this ] ( QObject * o ) {
// this whole call is here so that delete client->sock ends up auto-removing the map entry
// as a convenience.
2019-04-30 13:31:56 +03:00
Debug ( ) < < " Client nested 'on_destroyed' called " ;
2019-04-27 22:43:09 +03:00
auto client = clientsById . take ( id ) ;
if ( client ) {
if ( client ! = o ) {
Error ( ) < < " client != passed-in pointer to on_destroy in " < < __FILE__ < < " line " < < __LINE__ < < " . FIXME! " ;
}
2019-04-28 01:58:22 +03:00
Debug ( " client id %ld purged from map " , long ( id ) ) ;
2019-04-27 22:43:09 +03:00
}
} ;
connect ( ret , & QObject : : destroyed , this , on_destroyed ) ;
2019-04-29 20:52:40 +03:00
connect ( ret , & AbstractConnection : : lostConnection , this , [ this , id ] ( AbstractConnection * cl ) {
2019-04-28 14:59:01 +03:00
if ( auto client = dynamic_cast < Client * > ( cl ) ; client ) {
Debug ( ) < < client - > prettyName ( ) < < " lost connection " ;
killClient ( client ) ;
} else {
Error ( ) < < " Internal error: lostConnection callback received null client! (expected client id: " < < id < < " ) " ;
}
} ) ;
2019-04-27 22:43:09 +03:00
return ret ;
}
void TcpServer : : killClient ( Client * client )
{
if ( ! client )
return ;
2019-04-28 14:59:01 +03:00
Debug ( ) < < __FUNCTION__ < < " (id: " < < client - > id < < " ) " ;
2019-04-27 22:43:09 +03:00
clientsById . remove ( client - > id ) ; // ensure gone from map asap so future lookups fail
2019-04-30 19:21:47 +03:00
client - > do_disconnect ( ) ;
2019-04-27 22:43:09 +03:00
}
void TcpServer : : killClient ( qint64 id )
{
killClient ( clientsById . take ( id ) ) ;
}
Client : : Client ( qint64 id , TcpServer * srv , QTcpSocket * sock )
2019-04-29 20:52:40 +03:00
: AbstractConnection ( id , sock , /*maxBuffer=1MB*/ 1000000 ) , srv ( srv )
2019-04-27 22:43:09 +03:00
{
Debug ( ) < < __PRETTY_FUNCTION__ ;
2019-04-28 14:59:01 +03:00
socket = sock ;
on_connected ( ) ;
2019-04-27 22:43:09 +03:00
}
Client : : ~ Client ( )
{
Debug ( ) < < __PRETTY_FUNCTION__ ;
2019-04-28 14:59:01 +03:00
socket = nullptr ; // NB: we are a child of socket. this line here is added in case some day I make AbstractClient delete socket on destruct.
}
void Client : : on_readyRead ( )
{
Debug ( ) < < prettyName ( ) < < " " < < __FUNCTION__ ;
while ( socket - > canReadLine ( ) ) {
auto line = socket - > readLine ( ) ;
nReceived = line . size ( ) ;
line = line . trimmed ( ) ;
Debug ( ) < < " Got line: " < < line ;
2019-04-28 15:03:48 +03:00
lastGood = Util : : getTime ( ) ;
2019-04-30 13:43:34 +03:00
if ( line = = " exit " ) {
send ( " sayonara \n " ) ;
2019-04-30 19:21:47 +03:00
do_disconnect ( true ) ;
2019-04-30 13:43:34 +03:00
} else
send ( " Thanks fam. \n " ) ;
2019-04-28 14:59:01 +03:00
}
if ( socket - > bytesAvailable ( ) > MAX_BUFFER ) {
// bad server.. sending us garbage data not containing newlines. Kill connection.
Error ( ) < < QString ( " client has sent us more than %1 bytes without a newline! Bad client? (id: %2) " ) . arg ( MAX_BUFFER ) . arg ( id ) ;
2019-04-30 19:21:47 +03:00
do_disconnect ( ) ;
2019-04-28 14:59:01 +03:00
status = Bad ;
}
}
2019-04-30 19:21:47 +03:00
void Client : : do_disconnect ( bool graceful )
2019-04-28 14:59:01 +03:00
{
2019-04-30 13:31:56 +03:00
const bool wasConnected = socket ? socket - > state ( ) = = QAbstractSocket : : ConnectedState : false ;
2019-04-30 19:21:47 +03:00
AbstractConnection : : do_disconnect ( graceful ) ; // if 'graceful' *AND* was connected, a disconnected state will be entered later at which point we will delete socket.
2019-04-30 13:31:56 +03:00
if ( socket & & ( ! graceful | | ! wasConnected ) )
/// delete the socket if we weren't connected or if !graceful.
/// If graceful && connected, then a disconnect signal will be sent later and then we will
/// reenter here and delete the socket.
2019-04-30 13:32:16 +03:00
socket - > deleteLater ( ) ; // side-effect: will implicitly delete 'this' because we are a child of the socket!
2019-04-30 13:43:34 +03:00
else if ( socket & & graceful )
Debug ( ) < < __FUNCTION__ < < " (graceful); delayed socket delete (wait for disconnect) ... " ;
2019-04-28 14:59:01 +03:00
}
void Client : : do_ping ( )
{
send ( " Sup gee? \n " ) ;
2019-04-27 22:43:09 +03:00
}