198 lines
9.0 KiB
Plaintext
198 lines
9.0 KiB
Plaintext
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the documentation of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:FDL$
|
|
** 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.
|
|
**
|
|
** GNU Free Documentation License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Free
|
|
** Documentation License version 1.3 as published by the Free Software
|
|
** Foundation and appearing in the file included in the packaging of
|
|
** this file. Please review the following information to ensure
|
|
** the GNU Free Documentation License version 1.3 requirements
|
|
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
/*!
|
|
\example blockingfortuneclient
|
|
\title Blocking Fortune Client Example
|
|
\ingroup examples-network
|
|
\brief Demonstrates how to create a client for a network service.
|
|
|
|
\image blockingfortuneclient-example.png
|
|
|
|
QTcpSocket supports two general approaches to network programming:
|
|
|
|
\list
|
|
|
|
\li \e{The asynchronous (non-blocking) approach.} Operations are scheduled
|
|
and performed when control returns to Qt's event loop. When the operation
|
|
is finished, QTcpSocket emits a signal. For example,
|
|
QTcpSocket::connectToHost() returns immediately, and when the connection
|
|
has been established, QTcpSocket emits
|
|
\l{QTcpSocket::connected()}{connected()}.
|
|
|
|
\li \e{The synchronous (blocking) approach.} In non-GUI and multithreaded
|
|
applications, you can call the \c waitFor...() functions (e.g.,
|
|
QTcpSocket::waitForConnected()) to suspend the calling thread until the
|
|
operation has completed, instead of connecting to signals.
|
|
|
|
\endlist
|
|
|
|
The implementation is very similar to the
|
|
\l{fortuneclient}{Fortune Client} example, but instead of having
|
|
QTcpSocket as a member of the main class, doing asynchronous networking in
|
|
the main thread, we will do all network operations in a separate thread
|
|
and use QTcpSocket's blocking API.
|
|
|
|
The purpose of this example is to demonstrate a pattern that you can use
|
|
to simplify your networking code, without losing responsiveness in your
|
|
user interface. Use of Qt's blocking network API often leads to
|
|
simpler code, but because of its blocking behavior, it should only be used
|
|
in non-GUI threads to prevent the user interface from freezing. But
|
|
contrary to what many think, using threads with QThread does not
|
|
necessarily add unmanagable complexity to your application.
|
|
|
|
We will start with the FortuneThread class, which handles the network
|
|
code.
|
|
|
|
\snippet blockingfortuneclient/fortunethread.h 0
|
|
|
|
FortuneThread is a QThread subclass that provides an API for scheduling
|
|
requests for fortunes, and it has signals for delivering fortunes and
|
|
reporting errors. You can call requestNewFortune() to request a new
|
|
fortune, and the result is delivered by the newFortune() signal. If any
|
|
error occurs, the error() signal is emitted.
|
|
|
|
It's important to notice that requestNewFortune() is called from the main,
|
|
GUI thread, but the host name and port values it stores will be accessed
|
|
from FortuneThread's thread. Because we will be reading and writing
|
|
FortuneThread's data members from different threads concurrently, we use
|
|
QMutex to synchronize access.
|
|
|
|
\snippet blockingfortuneclient/fortunethread.cpp 2
|
|
|
|
The requestNewFortune() function stores the host name and port of the
|
|
fortune server as member data, and we lock the mutex with QMutexLocker to
|
|
protect this data. We then start the thread, unless it is already
|
|
running. We will come back to the QWaitCondition::wakeOne() call later.
|
|
|
|
\snippet blockingfortuneclient/fortunethread.cpp 4
|
|
\snippet blockingfortuneclient/fortunethread.cpp 5
|
|
|
|
In the run() function, we start by acquiring the mutex lock, fetching the
|
|
host name and port from the member data, and then releasing the lock
|
|
again. The case that we are protecting ourselves against is that \c
|
|
requestNewFortune() could be called at the same time as we are fetching
|
|
this data. QString is \l reentrant but \e not \l{thread-safe}, and we must
|
|
also avoid the unlikely risk of reading the host name from one request,
|
|
and port of another. And as you might have guessed, FortuneThread can only
|
|
handle one request at a time.
|
|
|
|
The run() function now enters a loop:
|
|
|
|
\snippet blockingfortuneclient/fortunethread.cpp 6
|
|
|
|
The loop will continue requesting fortunes for as long as \e quit is
|
|
false. We start our first request by creating a QTcpSocket on the stack,
|
|
and then we call \l{QTcpSocket::connectToHost()}{connectToHost()}. This
|
|
starts an asynchronous operation which, after control returns to Qt's
|
|
event loop, will cause QTcpSocket to emit
|
|
\l{QTcpSocket::connected()}{connected()} or
|
|
\l{QTcpSocket::error()}{error()}.
|
|
|
|
\snippet blockingfortuneclient/fortunethread.cpp 8
|
|
|
|
But since we are running in a non-GUI thread, we do not have to worry
|
|
about blocking the user interface. So instead of entering an event loop,
|
|
we simply call QTcpSocket::waitForConnected(). This function will wait,
|
|
blocking the calling thread, until QTcpSocket emits connected() or an
|
|
error occurs. If connected() is emitted, the function returns true; if the
|
|
connection failed or timed out (which in this example happens after 5
|
|
seconds), false is returned. QTcpSocket::waitForConnected(), like the
|
|
other \c waitFor...() functions, is part of QTcpSocket's \e{blocking
|
|
API}.
|
|
|
|
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.
|
|
|
|
\snippet blockingfortuneclient/fortunethread.cpp 12
|
|
|
|
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
|
|
|
|
The final part of our loop is that we acquire the mutex so that we can
|
|
safely read from our member data. We then let the thread go to sleep by
|
|
calling QWaitCondition::wait(). At this point, we can go back to
|
|
requestNewFortune() and look closed at the call to wakeOne():
|
|
|
|
\snippet blockingfortuneclient/fortunethread.cpp 1
|
|
\dots
|
|
\snippet blockingfortuneclient/fortunethread.cpp 3
|
|
|
|
What happened here was that because the thread falls asleep waiting for a
|
|
new request, we needed to wake it up again when a new request
|
|
arrives. QWaitCondition is often used in threads to signal a wakeup call
|
|
like this.
|
|
|
|
\snippet blockingfortuneclient/fortunethread.cpp 0
|
|
|
|
Finishing off the FortuneThread walkthrough, this is the destructor that
|
|
sets \e quit to true, wakes up the thread and waits for the thread to exit
|
|
before returning. This lets the \c while loop in run() will finish its current
|
|
iteration. When run() returns, the thread will terminate and be destroyed.
|
|
|
|
Now for the BlockingClient class:
|
|
|
|
\snippet blockingfortuneclient/blockingclient.h 0
|
|
|
|
BlockingClient is very similar to the Client class in the
|
|
\l{fortuneclient}{Fortune Client} example, but in this class
|
|
we store a FortuneThread member instead of a pointer to a QTcpSocket.
|
|
When the user clicks the "Get Fortune" button, the same slot is called,
|
|
but its implementation is slightly different:
|
|
|
|
\snippet blockingfortuneclient/blockingclient.cpp 0
|
|
|
|
We connect our FortuneThread's two signals newFortune() and error() (which
|
|
are somewhat similar to QTcpSocket::readyRead() and QTcpSocket::error() in
|
|
the previous example) to requestNewFortune() and displayError().
|
|
|
|
\snippet blockingfortuneclient/blockingclient.cpp 1
|
|
|
|
The requestNewFortune() slot calls FortuneThread::requestNewFortune(),
|
|
which \e shedules the request. When the thread has received a new fortune
|
|
and emits newFortune(), our showFortune() slot is called:
|
|
|
|
\snippet blockingfortuneclient/blockingclient.cpp 2
|
|
\codeline
|
|
\snippet blockingfortuneclient/blockingclient.cpp 3
|
|
|
|
Here, we simply display the fortune we received as the argument.
|
|
|
|
\sa {Fortune Client Example}, {Fortune Server Example}
|
|
*/
|