1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
/****************************************************************************
**
** 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 documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** GNU Free Documentation License
** 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.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms
** and conditions contained in a signed written agreement between you
** and Nokia.
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
/*!
\example network/blockingfortuneclient
\title Blocking Fortune Client Example
The Blocking Fortune Client example shows how to create a client for a
network service using QTcpSocket's synchronous API in a non-GUI thread.
\image blockingfortuneclient-example.png
QTcpSocket supports two general approaches to network programming:
\list
\o \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()}.
\o \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{network/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 examples/network/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 examples/network/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 examples/network/blockingfortuneclient/fortunethread.cpp 4
\snippet examples/network/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 examples/network/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 examples/network/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. Now it's
time to see what the fortune server has sent us.
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 9
\snippet examples/network/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.
\snippet examples/network/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.
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 12
\snippet examples/network/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 examples/network/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().
\snippet examples/network/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 examples/network/blockingfortuneclient/fortunethread.cpp 1
\dots
\snippet examples/network/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 examples/network/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 examples/network/blockingfortuneclient/blockingclient.h 0
BlockingClient is very similar to the Client class in the
\l{network/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 examples/network/blockingfortuneclient/blockingclient.cpp 0
\snippet examples/network/blockingfortuneclient/blockingclient.cpp 1
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 examples/network/blockingfortuneclient/blockingclient.cpp 2
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 examples/network/blockingfortuneclient/blockingclient.cpp 3
\codeline
\snippet examples/network/blockingfortuneclient/blockingclient.cpp 4
Here, we simply display the fortune we received as the argument.
\sa {Fortune Client Example}, {Fortune Server Example}
*/
|