qt5base-lts/examples/network/network-chat/connection.cpp

276 lines
7.5 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** 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 Nokia Corporation and its Subsidiary(-ies) 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();
}