summaryrefslogtreecommitdiffstats
path: root/examples/network
diff options
context:
space:
mode:
authorAlex Trotsenko <alex1973tr@gmail.com>2015-03-05 10:52:17 +0200
committerAlex Trotsenko <alex1973tr@gmail.com>2016-01-13 16:31:33 +0000
commit184d66caa5f7f93b7383319c5c8985524e0dc824 (patch)
tree272ce3f066d3c80f77dfca50fd0914b341c2f9da /examples/network
parentca6f11dcf207aa51ce32c9813ad1ab3790bb31ee (diff)
QDataStream: handle incomplete reads from QIODevice
This adds a way to resume reading from a stream after a ReadPastEnd error. This is done by introducing a stream read transaction mechanism that keeps read data in an internal buffer and rolls it back on failure. [ChangeLog][QtCore] Added QDataStream startTransaction(), commitTransaction(), rollbackTransaction(), abortTransaction() functions to support read transactions. Task-number: QTBUG-44418 Change-Id: Ibf946e1939a5573c4182fea7e26608947218c2d9 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
Diffstat (limited to 'examples/network')
-rw-r--r--examples/network/blockingfortuneclient/fortunethread.cpp28
-rw-r--r--examples/network/doc/src/blockingfortuneclient.qdoc33
-rw-r--r--examples/network/doc/src/fortuneclient.qdoc45
-rw-r--r--examples/network/doc/src/fortuneserver.qdoc13
-rw-r--r--examples/network/fortuneclient/client.cpp34
-rw-r--r--examples/network/fortuneclient/client.h3
-rw-r--r--examples/network/fortuneserver/server.cpp7
-rw-r--r--examples/network/threadedfortuneserver/fortunethread.cpp3
8 files changed, 55 insertions, 111 deletions
diff --git a/examples/network/blockingfortuneclient/fortunethread.cpp b/examples/network/blockingfortuneclient/fortunethread.cpp
index b948d504ae..a668e45d93 100644
--- a/examples/network/blockingfortuneclient/fortunethread.cpp
+++ b/examples/network/blockingfortuneclient/fortunethread.cpp
@@ -96,37 +96,27 @@ void FortuneThread::run()
emit error(socket.error(), socket.errorString());
return;
}
-//! [8] //! [9]
+//! [8] //! [11]
- while (socket.bytesAvailable() < (int)sizeof(quint16)) {
- if (!socket.waitForReadyRead(Timeout)) {
- emit error(socket.error(), socket.errorString());
- return;
- }
-//! [9] //! [10]
- }
-//! [10] //! [11]
-
- quint16 blockSize;
QDataStream in(&socket);
in.setVersion(QDataStream::Qt_4_0);
- in >> blockSize;
+ QString fortune;
//! [11] //! [12]
- while (socket.bytesAvailable() < blockSize) {
+ do {
if (!socket.waitForReadyRead(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
-//! [12] //! [13]
- }
-//! [13] //! [14]
+
+ in.startTransaction();
+ in >> fortune;
+ } while (!in.commitTransaction());
+//! [12] //! [15]
mutex.lock();
- QString fortune;
- in >> fortune;
emit newFortune(fortune);
-//! [7] //! [14] //! [15]
+//! [7]
cond.wait(&mutex);
serverName = hostName;
diff --git a/examples/network/doc/src/blockingfortuneclient.qdoc b/examples/network/doc/src/blockingfortuneclient.qdoc
index fbbfd466a5..12062fbe33 100644
--- a/examples/network/doc/src/blockingfortuneclient.qdoc
+++ b/examples/network/doc/src/blockingfortuneclient.qdoc
@@ -125,39 +125,22 @@
other \c waitFor...() functions, is part of QTcpSocket's \e{blocking
API}.
- After this statement, we have a connected socket to work with. Now it's
- time to see what the fortune server has sent us.
-
- \snippet blockingfortuneclient/fortunethread.cpp 9
- \snippet blockingfortuneclient/fortunethread.cpp 10
-
- This step is to read the size of the packet. Although we are only reading
- two bytes here, and the \c while loop may seem to overdo it, we present this
- code to demonstrate a good pattern for waiting for data using
- QTcpSocket::waitForReadyRead(). It goes like this: For as long as we still
- need more data, we call waitForReadyRead(). If it returns false,
- we abort the operation. After this statement, we know that we have received
- enough data.
+ After this statement, we have a connected socket to work with.
\snippet blockingfortuneclient/fortunethread.cpp 11
Now we can create a QDataStream object, passing the socket to
QDataStream's constructor, and as in the other client examples we set
- the stream protocol version to QDataStream::Qt_4_0, and read the size
- of the packet.
+ the stream protocol version to QDataStream::Qt_4_0.
\snippet blockingfortuneclient/fortunethread.cpp 12
- \snippet blockingfortuneclient/fortunethread.cpp 13
-
- Again, we'll use a loop that waits for more data by calling
- QTcpSocket::waitForReadyRead(). In this loop, we're waiting until
- QTcpSocket::bytesAvailable() returns the full packet size.
-
- \snippet blockingfortuneclient/fortunethread.cpp 14
- Now that we have all the data that we need, we can use QDataStream to
- read the fortune string from the packet. The resulting fortune is
- delivered by emitting newFortune().
+ We proceed by initiating a loop that waits for the fortune string data by
+ calling QTcpSocket::waitForReadyRead(). If it returns false, we abort the
+ operation. After this statement, we start a stream read transaction. We
+ exit the loop when QDataStream::commitTransaction() returns true, which
+ means successful fortune string loading. The resulting fortune is
+ delivered by emitting newFortune():
\snippet blockingfortuneclient/fortunethread.cpp 15
diff --git a/examples/network/doc/src/fortuneclient.qdoc b/examples/network/doc/src/fortuneclient.qdoc
index d9ebb575df..4d726fc5fa 100644
--- a/examples/network/doc/src/fortuneclient.qdoc
+++ b/examples/network/doc/src/fortuneclient.qdoc
@@ -41,8 +41,7 @@
request a line of text from a fortune server (from the
\l{fortuneserver}{Fortune Server} example). The client requests a
fortune by simply connecting to the server. The server then responds with
- a 16-bit (quint16) integer containing the length of the fortune text,
- followed by a QString.
+ a QString which contains the fortune text.
QTcpSocket supports two general approaches to network programming:
@@ -71,8 +70,8 @@
\snippet fortuneclient/client.h 0
Other than the widgets that make up the GUI, the data members include a
- QTcpSocket pointer, a copy of the fortune text currently displayed, and
- the size of the packet we are currently reading (more on this later).
+ QTcpSocket pointer, a QDataStream object that operates on the socket, and
+ a copy of the fortune text currently displayed.
The socket is initialized in the Client constructor. We'll pass the main
widget as parent, so that we won't have to worry about deleting the
@@ -82,6 +81,12 @@
\dots
\snippet fortuneclient/client.cpp 1
+ The protocol is based on QDataStream, so we set the stream device to the
+ newly created socket. We then explicitly set the protocol version of the
+ stream to QDataStream::Qt_4_0 to ensure that we're using the same version
+ as the fortune server, no matter which version of Qt the client and
+ server use.
+
The only QTcpSocket signals we need in this example are
QTcpSocket::readyRead(), signifying that data has been received, and
QTcpSocket::error(), which we will use to catch any connection errors:
@@ -96,8 +101,7 @@
\snippet fortuneclient/client.cpp 6
- In this slot, we initialize \c blockSize to 0, preparing to read a new block
- of data. Because we allow the user to click \uicontrol{Get Fortune} before the
+ Because we allow the user to click \uicontrol{Get Fortune} before the
previous connection finished closing, we start off by aborting the
previous connection by calling QTcpSocket::abort(). (On an unconnected
socket, this function does nothing.) We then proceed to connecting to the
@@ -131,31 +135,26 @@
signal is connected to \c Client::readFortune():
\snippet fortuneclient/client.cpp 8
- \codeline
- \snippet fortuneclient/client.cpp 10
-
- The protocol is based on QDataStream, so we start by creating a stream
- object, passing the socket to QDataStream's constructor. We then
- explicitly set the protocol version of the stream to QDataStream::Qt_4_0
- to ensure that we're using the same version as the fortune server, no
- matter which version of Qt the client and server use.
Now, TCP is based on sending a stream of data, so we cannot expect to get
the entire fortune in one go. Especially on a slow network, the data can
be received in several small fragments. QTcpSocket buffers up all incoming
data and emits \l{QTcpSocket::readyRead()}{readyRead()} for every new
block that arrives, and it is our job to ensure that we have received all
- the data we need before we start parsing. The server's response starts
- with the size of the packet, so first we need to ensure that we can read
- the size, then we will wait until QTcpSocket has received the full packet.
-
- \snippet fortuneclient/client.cpp 11
- \codeline
- \snippet fortuneclient/client.cpp 12
+ the data we need before we start parsing.
+ For this purpose we use a QDataStream read transaction. It keeps reading
+ stream data into an internal buffer and rolls it back in case of an
+ incomplete read. We start by calling startTransaction() which also resets
+ the stream status to indicate that new data was received on the socket.
We proceed by using QDataStream's streaming operator to read the fortune
- from the socket into a QString. Once read, we can call QLabel::setText()
- to display the fortune.
+ from the socket into a QString. Once read, we complete the transaction by
+ calling QDataStream::commitTransaction(). If we did not receive a full
+ packet, this function restores the stream data to the initial position,
+ after which we can wait for a new readyRead() signal.
+
+ After a successful read transaction, we call QLabel::setText() to display
+ the fortune.
\sa {Fortune Server Example}, {Blocking Fortune Client Example}
*/
diff --git a/examples/network/doc/src/fortuneserver.qdoc b/examples/network/doc/src/fortuneserver.qdoc
index 0b110783aa..5419882a62 100644
--- a/examples/network/doc/src/fortuneserver.qdoc
+++ b/examples/network/doc/src/fortuneserver.qdoc
@@ -73,17 +73,8 @@
using QTcpSocket. First we create a QByteArray and a QDataStream object,
passing the bytearray to QDataStream's constructor. We then explicitly set
the protocol version of QDataStream to QDataStream::Qt_4_0 to ensure that
- we can communicate with clients from future versions of Qt. (See
- QDataStream::setVersion().)
-
- \snippet fortuneserver/server.cpp 6
-
- At the start of our QByteArray, we reserve space for a 16 bit integer that
- will contain the total size of the data block we are sending. We continue
- by streaming in a random fortune. Then we seek back to the beginning of
- the QByteArray, and overwrite the reserved 16 bit integer value with the
- total size of the array. By doing this, we provide a way for clients to
- verify how much data they can expect before reading the whole packet.
+ we can communicate with clients from future versions of Qt (see
+ QDataStream::setVersion()). We continue by streaming in a random fortune.
\snippet fortuneserver/server.cpp 7
diff --git a/examples/network/fortuneclient/client.cpp b/examples/network/fortuneclient/client.cpp
index 42fed30445..a71b90dda8 100644
--- a/examples/network/fortuneclient/client.cpp
+++ b/examples/network/fortuneclient/client.cpp
@@ -49,10 +49,7 @@ Client::Client(QWidget *parent)
, hostCombo(new QComboBox)
, portLineEdit(new QLineEdit)
, getFortuneButton(new QPushButton(tr("Get Fortune")))
-//! [1]
, tcpSocket(new QTcpSocket(this))
-//! [1]
- , blockSize(0)
, networkSession(Q_NULLPTR)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -100,6 +97,11 @@ Client::Client(QWidget *parent)
buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
+//! [1]
+ in.setDevice(tcpSocket);
+ in.setVersion(QDataStream::Qt_4_0);
+//! [1]
+
connect(hostCombo, &QComboBox::editTextChanged,
this, &Client::enableGetFortuneButton);
connect(portLineEdit, &QLineEdit::textChanged,
@@ -171,7 +173,6 @@ Client::Client(QWidget *parent)
void Client::requestNewFortune()
{
getFortuneButton->setEnabled(false);
- blockSize = 0;
tcpSocket->abort();
//! [7]
tcpSocket->connectToHost(hostCombo->currentText(),
@@ -183,39 +184,24 @@ void Client::requestNewFortune()
//! [8]
void Client::readFortune()
{
-//! [9]
- QDataStream in(tcpSocket);
- in.setVersion(QDataStream::Qt_4_0);
-
- if (blockSize == 0) {
- if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
- return;
-//! [8]
-
-//! [10]
- in >> blockSize;
- }
-
- if (tcpSocket->bytesAvailable() < blockSize)
- return;
-//! [10] //! [11]
+ in.startTransaction();
QString nextFortune;
in >> nextFortune;
+ if (!in.commitTransaction())
+ return;
+
if (nextFortune == currentFortune) {
QTimer::singleShot(0, this, &Client::requestNewFortune);
return;
}
-//! [11]
-//! [12]
currentFortune = nextFortune;
-//! [9]
statusLabel->setText(currentFortune);
getFortuneButton->setEnabled(true);
}
-//! [12]
+//! [8]
//! [13]
void Client::displayError(QAbstractSocket::SocketError socketError)
diff --git a/examples/network/fortuneclient/client.h b/examples/network/fortuneclient/client.h
index 9b7d6f4dbf..3b07275ded 100644
--- a/examples/network/fortuneclient/client.h
+++ b/examples/network/fortuneclient/client.h
@@ -43,6 +43,7 @@
#include <QDialog>
#include <QTcpSocket>
+#include <QDataStream>
QT_BEGIN_NAMESPACE
class QComboBox;
@@ -75,8 +76,8 @@ private:
QPushButton *getFortuneButton;
QTcpSocket *tcpSocket;
+ QDataStream in;
QString currentFortune;
- quint16 blockSize;
QNetworkSession *networkSession;
};
diff --git a/examples/network/fortuneserver/server.cpp b/examples/network/fortuneserver/server.cpp
index 089f594cab..f318d58a6d 100644
--- a/examples/network/fortuneserver/server.cpp
+++ b/examples/network/fortuneserver/server.cpp
@@ -174,12 +174,9 @@ void Server::sendFortune()
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
-//! [4] //! [6]
- out << (quint16)0;
+
out << fortunes.at(qrand() % fortunes.size());
- out.device()->seek(0);
- out << (quint16)(block.size() - sizeof(quint16));
-//! [6] //! [7]
+//! [4] //! [7]
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection, &QAbstractSocket::disconnected,
diff --git a/examples/network/threadedfortuneserver/fortunethread.cpp b/examples/network/threadedfortuneserver/fortunethread.cpp
index 5e70dd08b9..ec5bb5a168 100644
--- a/examples/network/threadedfortuneserver/fortunethread.cpp
+++ b/examples/network/threadedfortuneserver/fortunethread.cpp
@@ -63,10 +63,7 @@ void FortuneThread::run()
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
- out << (quint16)0;
out << text;
- out.device()->seek(0);
- out << (quint16)(block.size() - sizeof(quint16));
//! [3] //! [4]
tcpSocket.write(block);