qtbase-opensource-src/examples/network/network-chat/connection.cpp

279 lines
8.2 KiB
C++
Raw Permalink Normal View History

2022-05-14 17:39:13 +08:00
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2018 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, 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;
/*
* Protocol is defined as follows, using the CBOR Data Definition Language:
*
* protocol = [
* greeting, ; must start with a greeting command
* * command ; zero or more regular commands after
* ]
* command = plaintext / ping / pong / greeting
* plaintext = { 0 => text }
* ping = { 1 => null }
* pong = { 2 => null }
* greeting = { 3 => text }
*/
Connection::Connection(QObject *parent)
: QTcpSocket(parent), writer(this)
{
greetingMessage = tr("undefined");
username = tr("unknown");
state = WaitingForGreeting;
currentDataType = Undefined;
transferTimerId = -1;
isGreetingMessageSent = false;
pingTimer.setInterval(PingInterval);
connect(this, &QTcpSocket::readyRead, this,
&Connection::processReadyRead);
connect(this, &QTcpSocket::disconnected,
&pingTimer, &QTimer::stop);
connect(&pingTimer, &QTimer::timeout,
this, &Connection::sendPing);
connect(this, &QTcpSocket::connected,
this, &Connection::sendGreetingMessage);
}
Connection::Connection(qintptr socketDescriptor, QObject *parent)
: Connection(parent)
{
setSocketDescriptor(socketDescriptor);
reader.setDevice(this);
}
Connection::~Connection()
{
if (isGreetingMessageSent) {
// Indicate clean shutdown.
writer.endArray();
waitForBytesWritten(2000);
}
}
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;
writer.startMap(1);
writer.append(PlainText);
writer.append(message);
writer.endMap();
return true;
}
void Connection::timerEvent(QTimerEvent *timerEvent)
{
if (timerEvent->timerId() == transferTimerId) {
abort();
killTimer(transferTimerId);
transferTimerId = -1;
}
}
void Connection::processReadyRead()
{
// we've got more data, let's parse
reader.reparse();
while (reader.lastError() == QCborError::NoError) {
if (state == WaitingForGreeting) {
if (!reader.isArray())
break; // protocol error
reader.enterContainer(); // we'll be in this array forever
state = ReadingGreeting;
} else if (reader.containerDepth() == 1) {
// Current state: no command read
// Next state: read command ID
if (!reader.hasNext()) {
reader.leaveContainer();
disconnectFromHost();
return;
}
if (!reader.isMap() || !reader.isLengthKnown() || reader.length() != 1)
break; // protocol error
reader.enterContainer();
} else if (currentDataType == Undefined) {
// Current state: read command ID
// Next state: read command payload
if (!reader.isInteger())
break; // protocol error
currentDataType = DataType(reader.toInteger());
reader.next();
} else {
// Current state: read command payload
if (reader.isString()) {
auto r = reader.readString();
buffer += r.data;
if (r.status != QCborStreamReader::EndOfString)
continue;
} else if (reader.isNull()) {
reader.next();
} else {
break; // protocol error
}
// Next state: no command read
reader.leaveContainer();
if (transferTimerId != -1) {
killTimer(transferTimerId);
transferTimerId = -1;
}
if (state == ReadingGreeting) {
if (currentDataType != Greeting)
break; // protocol error
processGreeting();
} else {
processData();
}
}
}
if (reader.lastError() != QCborError::EndOfFile)
abort(); // parse error
if (transferTimerId != -1 && reader.containerDepth() > 1)
transferTimerId = startTimer(TransferTimeout);
}
void Connection::sendPing()
{
if (pongTime.elapsed() > PongTimeout) {
abort();
return;
}
writer.startMap(1);
writer.append(Ping);
writer.append(nullptr); // no payload
writer.endMap();
}
void Connection::sendGreetingMessage()
{
writer.startArray(); // this array never ends
writer.startMap(1);
writer.append(Greeting);
writer.append(greetingMessage);
writer.endMap();
isGreetingMessageSent = true;
if (!reader.device())
reader.setDevice(this);
}
void Connection::processGreeting()
{
username = buffer + '@' + peerAddress().toString() + ':'
+ QString::number(peerPort());
currentDataType = Undefined;
buffer.clear();
if (!isValid()) {
abort();
return;
}
if (!isGreetingMessageSent)
sendGreetingMessage();
pingTimer.start();
pongTime.start();
state = ReadyForUse;
emit readyForUse();
}
void Connection::processData()
{
switch (currentDataType) {
case PlainText:
emit newMessage(username, buffer);
break;
case Ping:
writer.startMap(1);
writer.append(Pong);
writer.append(nullptr); // no payload
writer.endMap();
break;
case Pong:
pongTime.restart();
break;
default:
break;
}
currentDataType = Undefined;
buffer.clear();
}