summaryrefslogtreecommitdiffstats
path: root/examples/network/network-chat/connection.cpp
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2018-01-17 18:27:09 -0800
committerThiago Macieira <thiago.macieira@intel.com>2018-06-02 03:11:30 +0000
commitbfcb8c6dcaac8563226dffb4d2d3c1fb6e666683 (patch)
treee0f7f29a8d43dbb6f7e92e30b07d7cd6455e499e /examples/network/network-chat/connection.cpp
parentc901cdadc0a6ec65eddfe4f181274e56567f9973 (diff)
Examples: change the main network-chat protocol to CBOR
This complements the previous commit, which changed the broadcast datagram to CBOR. This commit changes the TCP protocol too. The protocol is an infinite array of commands, each of which is a map from an integer (the DataType enum) to either a string or null. The entire state machine for the connection is rewritten, relying on QCborStreamReader's ability to deal with incomplete data. Change-Id: Ic38ec929fc3f4bb795dafffd150ac674c32fac87 Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'examples/network/network-chat/connection.cpp')
-rw-r--r--examples/network/network-chat/connection.cpp264
1 files changed, 127 insertions, 137 deletions
diff --git a/examples/network/network-chat/connection.cpp b/examples/network/network-chat/connection.cpp
index 332d5dc56b..58cf67eb6d 100644
--- a/examples/network/network-chat/connection.cpp
+++ b/examples/network/network-chat/connection.cpp
@@ -1,6 +1,7 @@
/****************************************************************************
**
** 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.
@@ -55,17 +56,29 @@
static const int TransferTimeout = 30 * 1000;
static const int PongTimeout = 60 * 1000;
static const int PingInterval = 5 * 1000;
-static const char SeparatorToken = ' ';
+
+/*
+ * 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)
+ : QTcpSocket(parent), writer(this)
{
greetingMessage = tr("undefined");
username = tr("unknown");
state = WaitingForGreeting;
currentDataType = Undefined;
- numBytesForCurrentDataType = -1;
- transferTimerId = 0;
+ transferTimerId = -1;
isGreetingMessageSent = false;
pingTimer.setInterval(PingInterval);
@@ -76,6 +89,22 @@ Connection::Connection(QObject *parent)
this, SLOT(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;
@@ -91,9 +120,11 @@ 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();
+ writer.startMap(1);
+ writer.append(PlainText);
+ writer.append(message);
+ writer.endMap();
+ return true;
}
void Connection::timerEvent(QTimerEvent *timerEvent)
@@ -101,61 +132,75 @@ void Connection::timerEvent(QTimerEvent *timerEvent)
if (timerEvent->timerId() == transferTimerId) {
abort();
killTimer(transferTimerId);
- transferTimerId = 0;
+ transferTimerId = -1;
}
}
void Connection::processReadyRead()
{
- if (state == WaitingForGreeting) {
- if (!readProtocolHeader())
- return;
- if (currentDataType != Greeting) {
- abort();
- return;
+ // 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();
+ }
}
- 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();
- }
+ if (reader.lastError() != QCborError::EndOfFile)
+ abort(); // parse error
- do {
- if (currentDataType == Undefined) {
- if (!readProtocolHeader())
- return;
- }
- if (!hasEnoughData())
- return;
- processData();
- } while (bytesAvailable() > 0);
+ if (transferTimerId != -1 && reader.containerDepth() > 1)
+ transferTimerId = startTimer(TransferTimeout);
}
void Connection::sendPing()
@@ -165,112 +210,58 @@ void Connection::sendPing()
return;
}
- write("PING 1 p");
+ writer.startMap(1);
+ writer.append(Ping);
+ writer.append(nullptr); // no payload
+ writer.endMap();
}
void Connection::sendGreetingMessage()
{
- QByteArray greeting = greetingMessage.toUtf8();
- QByteArray data = "GREETING " + QByteArray::number(greeting.size()) + ' ' + greeting;
- if (write(data) == data.size())
- isGreetingMessageSent = true;
-}
+ writer.startArray(); // this array never ends
-int Connection::readDataIntoBuffer(int maxSize)
-{
- if (maxSize > MaxBufferSize)
- return 0;
+ writer.startMap(1);
+ writer.append(Greeting);
+ writer.append(greetingMessage);
+ writer.endMap();
+ isGreetingMessageSent = true;
- 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;
+ if (!reader.device())
+ reader.setDevice(this);
}
-int Connection::dataLengthForCurrentDataType()
+void Connection::processGreeting()
{
- if (bytesAvailable() <= 0 || readDataIntoBuffer() <= 0
- || !buffer.endsWith(SeparatorToken))
- return 0;
-
- buffer.chop(1);
- int number = buffer.toInt();
+ username = buffer + '@' + peerAddress().toString() + ':'
+ + QString::number(peerPort());
+ currentDataType = Undefined;
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;
+ if (!isValid()) {
abort();
- return false;
- }
-
- buffer.clear();
- numBytesForCurrentDataType = dataLengthForCurrentDataType();
- return true;
-}
-
-bool Connection::hasEnoughData()
-{
- if (transferTimerId) {
- QObject::killTimer(transferTimerId);
- transferTimerId = 0;
+ return;
}
- if (numBytesForCurrentDataType <= 0)
- numBytesForCurrentDataType = dataLengthForCurrentDataType();
-
- if (bytesAvailable() < numBytesForCurrentDataType
- || numBytesForCurrentDataType <= 0) {
- transferTimerId = startTimer(TransferTimeout);
- return false;
- }
+ if (!isGreetingMessageSent)
+ sendGreetingMessage();
- return true;
+ pingTimer.start();
+ pongTime.start();
+ state = ReadyForUse;
+ emit readyForUse();
}
void Connection::processData()
{
- buffer = read(numBytesForCurrentDataType);
- if (buffer.size() != numBytesForCurrentDataType) {
- abort();
- return;
- }
-
switch (currentDataType) {
case PlainText:
- emit newMessage(username, QString::fromUtf8(buffer));
+ emit newMessage(username, buffer);
break;
case Ping:
- write("PONG 1 p");
+ writer.startMap(1);
+ writer.append(Pong);
+ writer.append(nullptr); // no payload
+ writer.endMap();
break;
case Pong:
pongTime.restart();
@@ -280,6 +271,5 @@ void Connection::processData()
}
currentDataType = Undefined;
- numBytesForCurrentDataType = 0;
buffer.clear();
}