#include "TcpServer.h" #include #include TcpServerError::~TcpServerError() {} // for vtable TcpServer::TcpServer(const QHostAddress &a, quint16 p) : QTcpServer(nullptr), IdMixin(newId()), addr(a), port(p) { _thread.setObjectName(prettyName()); setObjectName(prettyName()); } TcpServer::~TcpServer() { Debug() << __FUNCTION__; stop(); } QString TcpServer::hostPort() const { return QString("%1:%2").arg(addr.toString()).arg(port); } QString TcpServer::prettyName() const { return QString("Srv %1 (id: %2)").arg(hostPort()).arg(id); } void TcpServer::tryStart() { if (!_thread.isRunning()) { ThreadObjectMixin::start(); // call super Log() << "Starting listener service for " << prettyName() << " ..."; if (auto result = chan.get(); result != "ok") { 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."; ThreadObjectMixin::on_finished(); } 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); newClient(sock); } else { Warning() << __FUNCTION__ << ": nextPendingConnection returned a nullptr! Called at the wrong time? FIXME!"; } } 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. Debug() << "Client nested 'on_destroyed' called"; auto client = clientsById.take(id); if (client) { if (client != o) { Error() << " client != passed-in pointer to on_destroy in " << __FILE__ << " line " << __LINE__ << ". FIXME!"; } Debug("client id %ld purged from map", long(id)); } }; connect(ret, &QObject::destroyed, this, on_destroyed); connect(ret, &AbstractConnection::lostConnection, this, [this,id](AbstractConnection *cl){ if (auto client = dynamic_cast(cl) ; client) { Debug() << client->prettyName() << " lost connection"; killClient(client); } else { Error() << "Internal error: lostConnection callback received null client! (expected client id: " << id << ")"; } }); return ret; } void TcpServer::killClient(Client *client) { if (!client) return; Debug() << __FUNCTION__ << " (id: " << client->id << ")"; clientsById.remove(client->id); // ensure gone from map asap so future lookups fail client->do_disconnect(); } void TcpServer::killClient(qint64 id) { killClient(clientsById.take(id)); } Client::Client(qint64 id, TcpServer *srv, QTcpSocket *sock) : AbstractConnection(id, sock, /*maxBuffer=1MB*/1000000), srv(srv) { Debug() << __PRETTY_FUNCTION__; socket = sock; on_connected(); } Client::~Client() { Debug() << __PRETTY_FUNCTION__; 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; lastGood = Util::getTime(); if (line == "exit") { send("sayonara\n"); do_disconnect(true); } else send("Thanks fam.\n"); } 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); do_disconnect(); status = Bad; } } void Client::do_disconnect(bool graceful) { const bool wasConnected = socket ? socket->state() == QAbstractSocket::ConnectedState : false; AbstractConnection::do_disconnect(graceful); // if 'graceful' *AND* was connected, a disconnected state will be entered later at which point we will delete socket. 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. socket->deleteLater(); // side-effect: will implicitly delete 'this' because we are a child of the socket! else if (socket && graceful) Debug() << __FUNCTION__ << " (graceful); delayed socket delete (wait for disconnect) ..."; } void Client::do_ping() { send("Sup gee?\n"); }