0
|
1 |
/****************************************************************************
|
|
2 |
**
|
|
3 |
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
|
4 |
** All rights reserved.
|
|
5 |
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
6 |
**
|
|
7 |
** This file is part of the documentation of the Qt Toolkit.
|
|
8 |
**
|
|
9 |
** $QT_BEGIN_LICENSE:LGPL$
|
|
10 |
** No Commercial Usage
|
|
11 |
** This file contains pre-release code and may not be distributed.
|
|
12 |
** You may use this file in accordance with the terms and conditions
|
|
13 |
** contained in the Technology Preview License Agreement accompanying
|
|
14 |
** this package.
|
|
15 |
**
|
|
16 |
** GNU Lesser General Public License Usage
|
|
17 |
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
18 |
** General Public License version 2.1 as published by the Free Software
|
|
19 |
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
20 |
** packaging of this file. Please review the following information to
|
|
21 |
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
22 |
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
23 |
**
|
|
24 |
** In addition, as a special exception, Nokia gives you certain additional
|
|
25 |
** rights. These rights are described in the Nokia Qt LGPL Exception
|
|
26 |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
27 |
**
|
|
28 |
** If you have questions regarding the use of this file, please contact
|
|
29 |
** Nokia at qt-info@nokia.com.
|
|
30 |
**
|
|
31 |
**
|
|
32 |
**
|
|
33 |
**
|
|
34 |
**
|
|
35 |
**
|
|
36 |
**
|
|
37 |
**
|
|
38 |
** $QT_END_LICENSE$
|
|
39 |
**
|
|
40 |
****************************************************************************/
|
|
41 |
|
|
42 |
/*!
|
|
43 |
\example network/blockingfortuneclient
|
|
44 |
\title Blocking Fortune Client Example
|
|
45 |
|
|
46 |
The Blocking Fortune Client example shows how to create a client for a
|
|
47 |
network service using QTcpSocket's synchronous API in a non-GUI thread.
|
|
48 |
|
|
49 |
\image blockingfortuneclient-example.png
|
|
50 |
|
|
51 |
QTcpSocket supports two general approaches to network programming:
|
|
52 |
|
|
53 |
\list
|
|
54 |
|
|
55 |
\o \e{The asynchronous (non-blocking) approach.} Operations are scheduled
|
|
56 |
and performed when control returns to Qt's event loop. When the operation
|
|
57 |
is finished, QTcpSocket emits a signal. For example,
|
|
58 |
QTcpSocket::connectToHost() returns immediately, and when the connection
|
|
59 |
has been established, QTcpSocket emits
|
|
60 |
\l{QTcpSocket::connected()}{connected()}.
|
|
61 |
|
|
62 |
\o \e{The synchronous (blocking) approach.} In non-GUI and multithreaded
|
|
63 |
applications, you can call the \c waitFor...() functions (e.g.,
|
|
64 |
QTcpSocket::waitForConnected()) to suspend the calling thread until the
|
|
65 |
operation has completed, instead of connecting to signals.
|
|
66 |
|
|
67 |
\endlist
|
|
68 |
|
|
69 |
The implementation is very similar to the
|
|
70 |
\l{network/fortuneclient}{Fortune Client} example, but instead of having
|
|
71 |
QTcpSocket as a member of the main class, doing asynchronous networking in
|
|
72 |
the main thread, we will do all network operations in a separate thread
|
|
73 |
and use QTcpSocket's blocking API.
|
|
74 |
|
|
75 |
The purpose of this example is to demonstrate a pattern that you can use
|
|
76 |
to simplify your networking code, without losing responsiveness in your
|
|
77 |
user interface. Use of Qt's blocking network API often leads to
|
|
78 |
simpler code, but because of its blocking behavior, it should only be used
|
|
79 |
in non-GUI threads to prevent the user interface from freezing. But
|
|
80 |
contrary to what many think, using threads with QThread does not
|
|
81 |
necessarily add unmanagable complexity to your application.
|
|
82 |
|
|
83 |
We will start with the FortuneThread class, which handles the network
|
|
84 |
code.
|
|
85 |
|
|
86 |
\snippet examples/network/blockingfortuneclient/fortunethread.h 0
|
|
87 |
|
|
88 |
FortuneThread is a QThread subclass that provides an API for scheduling
|
|
89 |
requests for fortunes, and it has signals for delivering fortunes and
|
|
90 |
reporting errors. You can call requestNewFortune() to request a new
|
|
91 |
fortune, and the result is delivered by the newFortune() signal. If any
|
|
92 |
error occurs, the error() signal is emitted.
|
|
93 |
|
|
94 |
It's important to notice that requestNewFortune() is called from the main,
|
|
95 |
GUI thread, but the host name and port values it stores will be accessed
|
|
96 |
from FortuneThread's thread. Because we will be reading and writing
|
|
97 |
FortuneThread's data members from different threads concurrently, we use
|
|
98 |
QMutex to synchronize access.
|
|
99 |
|
|
100 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 2
|
|
101 |
|
|
102 |
The requestNewFortune() function stores the host name and port of the
|
|
103 |
fortune server as member data, and we lock the mutex with QMutexLocker to
|
|
104 |
protect this data. We then start the thread, unless it is already
|
|
105 |
running. We will come back to the QWaitCondition::wakeOne() call later.
|
|
106 |
|
|
107 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 4
|
|
108 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 5
|
|
109 |
|
|
110 |
In the run() function, we start by acquiring the mutex lock, fetching the
|
|
111 |
host name and port from the member data, and then releasing the lock
|
|
112 |
again. The case that we are protecting ourselves against is that \c
|
|
113 |
requestNewFortune() could be called at the same time as we are fetching
|
|
114 |
this data. QString is \l reentrant but \e not \l{thread-safe}, and we must
|
|
115 |
also avoid the unlikely risk of reading the host name from one request,
|
|
116 |
and port of another. And as you might have guessed, FortuneThread can only
|
|
117 |
handle one request at a time.
|
|
118 |
|
|
119 |
The run() function now enters a loop:
|
|
120 |
|
|
121 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 6
|
|
122 |
|
|
123 |
The loop will continue requesting fortunes for as long as \e quit is
|
|
124 |
false. We start our first request by creating a QTcpSocket on the stack,
|
|
125 |
and then we call \l{QTcpSocket::connectToHost()}{connectToHost()}. This
|
|
126 |
starts an asynchronous operation which, after control returns to Qt's
|
|
127 |
event loop, will cause QTcpSocket to emit
|
|
128 |
\l{QTcpSocket::connected()}{connected()} or
|
|
129 |
\l{QTcpSocket::error()}{error()}.
|
|
130 |
|
|
131 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 8
|
|
132 |
|
|
133 |
But since we are running in a non-GUI thread, we do not have to worry
|
|
134 |
about blocking the user interface. So instead of entering an event loop,
|
|
135 |
we simply call QTcpSocket::waitForConnected(). This function will wait,
|
|
136 |
blocking the calling thread, until QTcpSocket emits connected() or an
|
|
137 |
error occurs. If connected() is emitted, the function returns true; if the
|
|
138 |
connection failed or timed out (which in this example happens after 5
|
|
139 |
seconds), false is returned. QTcpSocket::waitForConnected(), like the
|
|
140 |
other \c waitFor...() functions, is part of QTcpSocket's \e{blocking
|
|
141 |
API}.
|
|
142 |
|
|
143 |
After this statement, we have a connected socket to work with. Now it's
|
|
144 |
time to see what the fortune server has sent us.
|
|
145 |
|
|
146 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 9
|
|
147 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 10
|
|
148 |
|
|
149 |
This step is to read the size of the packet. Although we are only reading
|
|
150 |
two bytes here, and the \c while loop may seem to overdo it, we present this
|
|
151 |
code to demonstrate a good pattern for waiting for data using
|
|
152 |
QTcpSocket::waitForReadyRead(). It goes like this: For as long as we still
|
|
153 |
need more data, we call waitForReadyRead(). If it returns false,
|
|
154 |
we abort the operation. After this statement, we know that we have received
|
|
155 |
enough data.
|
|
156 |
|
|
157 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 11
|
|
158 |
|
|
159 |
Now we can create a QDataStream object, passing the socket to
|
|
160 |
QDataStream's constructor, and as in the other client examples we set
|
|
161 |
the stream protocol version to QDataStream::Qt_4_0, and read the size
|
|
162 |
of the packet.
|
|
163 |
|
|
164 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 12
|
|
165 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 13
|
|
166 |
|
|
167 |
Again, we'll use a loop that waits for more data by calling
|
|
168 |
QTcpSocket::waitForReadyRead(). In this loop, we're waiting until
|
|
169 |
QTcpSocket::bytesAvailable() returns the full packet size.
|
|
170 |
|
|
171 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 14
|
|
172 |
|
|
173 |
Now that we have all the data that we need, we can use QDataStream to
|
|
174 |
read the fortune string from the packet. The resulting fortune is
|
|
175 |
delivered by emitting newFortune().
|
|
176 |
|
|
177 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 15
|
|
178 |
|
|
179 |
The final part of our loop is that we acquire the mutex so that we can
|
|
180 |
safely read from our member data. We then let the thread go to sleep by
|
|
181 |
calling QWaitCondition::wait(). At this point, we can go back to
|
|
182 |
requestNewFortune() and look closed at the call to wakeOne():
|
|
183 |
|
|
184 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 1
|
|
185 |
\dots
|
|
186 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 3
|
|
187 |
|
|
188 |
What happened here was that because the thread falls asleep waiting for a
|
|
189 |
new request, we needed to wake it up again when a new request
|
|
190 |
arrives. QWaitCondition is often used in threads to signal a wakeup call
|
|
191 |
like this.
|
|
192 |
|
|
193 |
\snippet examples/network/blockingfortuneclient/fortunethread.cpp 0
|
|
194 |
|
|
195 |
Finishing off the FortuneThread walkthrough, this is the destructor that
|
|
196 |
sets \e quit to true, wakes up the thread and waits for the thread to exit
|
|
197 |
before returning. This lets the \c while loop in run() will finish its current
|
|
198 |
iteration. When run() returns, the thread will terminate and be destroyed.
|
|
199 |
|
|
200 |
Now for the BlockingClient class:
|
|
201 |
|
|
202 |
\snippet examples/network/blockingfortuneclient/blockingclient.h 0
|
|
203 |
|
|
204 |
BlockingClient is very similar to the Client class in the
|
|
205 |
\l{network/fortuneclient}{Fortune Client} example, but in this class
|
|
206 |
we store a FortuneThread member instead of a pointer to a QTcpSocket.
|
|
207 |
When the user clicks the "Get Fortune" button, the same slot is called,
|
|
208 |
but its implementation is slightly different:
|
|
209 |
|
|
210 |
\snippet examples/network/blockingfortuneclient/blockingclient.cpp 0
|
|
211 |
\snippet examples/network/blockingfortuneclient/blockingclient.cpp 1
|
|
212 |
|
|
213 |
We connect our FortuneThread's two signals newFortune() and error() (which
|
|
214 |
are somewhat similar to QTcpSocket::readyRead() and QTcpSocket::error() in
|
|
215 |
the previous example) to requestNewFortune() and displayError().
|
|
216 |
|
|
217 |
\snippet examples/network/blockingfortuneclient/blockingclient.cpp 2
|
|
218 |
|
|
219 |
The requestNewFortune() slot calls FortuneThread::requestNewFortune(),
|
|
220 |
which \e shedules the request. When the thread has received a new fortune
|
|
221 |
and emits newFortune(), our showFortune() slot is called:
|
|
222 |
|
|
223 |
\snippet examples/network/blockingfortuneclient/blockingclient.cpp 3
|
|
224 |
\codeline
|
|
225 |
\snippet examples/network/blockingfortuneclient/blockingclient.cpp 4
|
|
226 |
|
|
227 |
Here, we simply display the fortune we received as the argument.
|
|
228 |
|
|
229 |
\sa {Fortune Client Example}, {Fortune Server Example}
|
|
230 |
*/
|