- Qt 5.5
- Qt Network
- Network Chat Example
- connection.cpp Example File
- Qt 5.5.1 Reference Documentation

# connection.cpp Example File

network-chat/connection.cpp

    /****************************************************************************
    **
    ** Copyright (C) 2015 The Qt Company Ltd.
    ** Contact: http://www.qt.io/licensing/
    **
    ** This file is part of the examples of the Qt Toolkit.
    **
    ** $QT_BEGIN_LICENSE:BSD$
    ** You may use this file under the terms of the BSD license as follows:
    **
    ** "Redistribution and use in source and binary forms, with or without
    ** modification, are permitted provided that the following conditions are
    ** met:
    **   * Redistributions of source code must retain the above copyright
    **     notice, this list of conditions and the following disclaimer.
    **   * Redistributions in binary form must reproduce the above copyright
    **     notice, this list of conditions and the following disclaimer in
    **     the documentation and/or other materials provided with the
    **     distribution.
    **   * Neither the name of The Qt Company Ltd nor the names of its
    **     contributors may be used to endorse or promote products derived
    **     from this software without specific prior written permission.
    **
    **
    ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
    **
    ** $QT_END_LICENSE$
    **
    ****************************************************************************/

    #include "connection.h"

    #include <QtNetwork>

    static const int TransferTimeout = 30 * 1000;
    static const int PongTimeout = 60 * 1000;
    static const int PingInterval = 5 * 1000;
    static const char SeparatorToken = ' ';

    Connection::Connection(QObject *parent)
        : QTcpSocket(parent)
    {
        greetingMessage = tr("undefined");
        username = tr("unknown");
        state = WaitingForGreeting;
        currentDataType = Undefined;
        numBytesForCurrentDataType = -1;
        transferTimerId = 0;
        isGreetingMessageSent = false;
        pingTimer.setInterval(PingInterval);

        QObject::connect(this, SIGNAL(readyRead()), this, SLOT(processReadyRead()));
        QObject::connect(this, SIGNAL(disconnected()), &pingTimer, SLOT(stop()));
        QObject::connect(&pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
        QObject::connect(this, SIGNAL(connected()),
                         this, SLOT(sendGreetingMessage()));
    }

    QString Connection::name() const
    {
        return username;
    }

    void Connection::setGreetingMessage(const QString &message)
    {
        greetingMessage = message;
    }

    bool Connection::sendMessage(const QString &message)
    {
        if (message.isEmpty())
            return false;

        QByteArray msg = message.toUtf8();
        QByteArray data = "MESSAGE " + QByteArray::number(msg.size()) + ' ' + msg;
        return write(data) == data.size();
    }

    void Connection::timerEvent(QTimerEvent *timerEvent)
    {
        if (timerEvent->timerId() == transferTimerId) {
            abort();
            killTimer(transferTimerId);
            transferTimerId = 0;
        }
    }

    void Connection::processReadyRead()
    {
        if (state == WaitingForGreeting) {
            if (!readProtocolHeader())
                return;
            if (currentDataType != Greeting) {
                abort();
                return;
            }
            state = ReadingGreeting;
        }

        if (state == ReadingGreeting) {
            if (!hasEnoughData())
                return;

            buffer = read(numBytesForCurrentDataType);
            if (buffer.size() != numBytesForCurrentDataType) {
                abort();
                return;
            }

            username = QString(buffer) + '@' + peerAddress().toString() + ':'
                       + QString::number(peerPort());
            currentDataType = Undefined;
            numBytesForCurrentDataType = 0;
            buffer.clear();

            if (!isValid()) {
                abort();
                return;
            }

            if (!isGreetingMessageSent)
                sendGreetingMessage();

            pingTimer.start();
            pongTime.start();
            state = ReadyForUse;
            emit readyForUse();
        }

        do {
            if (currentDataType == Undefined) {
                if (!readProtocolHeader())
                    return;
            }
            if (!hasEnoughData())
                return;
            processData();
        } while (bytesAvailable() > 0);
    }

    void Connection::sendPing()
    {
        if (pongTime.elapsed() > PongTimeout) {
            abort();
            return;
        }

        write("PING 1 p");
    }

    void Connection::sendGreetingMessage()
    {
        QByteArray greeting = greetingMessage.toUtf8();
        QByteArray data = "GREETING " + QByteArray::number(greeting.size()) + ' ' + greeting;
        if (write(data) == data.size())
            isGreetingMessageSent = true;
    }

    int Connection::readDataIntoBuffer(int maxSize)
    {
        if (maxSize > MaxBufferSize)
            return 0;

        int numBytesBeforeRead = buffer.size();
        if (numBytesBeforeRead == MaxBufferSize) {
            abort();
            return 0;
        }

        while (bytesAvailable() > 0 && buffer.size() < maxSize) {
            buffer.append(read(1));
            if (buffer.endsWith(SeparatorToken))
                break;
        }
        return buffer.size() - numBytesBeforeRead;
    }

    int Connection::dataLengthForCurrentDataType()
    {
        if (bytesAvailable() <= 0 || readDataIntoBuffer() <= 0
                || !buffer.endsWith(SeparatorToken))
            return 0;

        buffer.chop(1);
        int number = buffer.toInt();
        buffer.clear();
        return number;
    }

    bool Connection::readProtocolHeader()
    {
        if (transferTimerId) {
            killTimer(transferTimerId);
            transferTimerId = 0;
        }

        if (readDataIntoBuffer() <= 0) {
            transferTimerId = startTimer(TransferTimeout);
            return false;
        }

        if (buffer == "PING ") {
            currentDataType = Ping;
        } else if (buffer == "PONG ") {
            currentDataType = Pong;
        } else if (buffer == "MESSAGE ") {
            currentDataType = PlainText;
        } else if (buffer == "GREETING ") {
            currentDataType = Greeting;
        } else {
            currentDataType = Undefined;
            abort();
            return false;
        }

        buffer.clear();
        numBytesForCurrentDataType = dataLengthForCurrentDataType();
        return true;
    }

    bool Connection::hasEnoughData()
    {
        if (transferTimerId) {
            QObject::killTimer(transferTimerId);
            transferTimerId = 0;
        }

        if (numBytesForCurrentDataType <= 0)
            numBytesForCurrentDataType = dataLengthForCurrentDataType();

        if (bytesAvailable() < numBytesForCurrentDataType
                || numBytesForCurrentDataType <= 0) {
            transferTimerId = startTimer(TransferTimeout);
            return false;
        }

        return true;
    }

    void Connection::processData()
    {
        buffer = read(numBytesForCurrentDataType);
        if (buffer.size() != numBytesForCurrentDataType) {
            abort();
            return;
        }

        switch (currentDataType) {
        case PlainText:
            emit newMessage(username, QString::fromUtf8(buffer));
            break;
        case Ping:
            write("PONG 1 p");
            break;
        case Pong:
            pongTime.restart();
            break;
        default:
            break;
        }

        currentDataType = Undefined;
        numBytesForCurrentDataType = 0;
        buffer.clear();
    }