summaryrefslogtreecommitdiffstats
path: root/examples/network/doc/src/fortuneclient.qdoc
blob: 054937e89318ce0fadee90f8507109108ee60567 (plain)
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
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only

/*!
    \example fortuneclient
    \title Fortune Client
    \examplecategory {Networking}
    \meta tags {tcp,network}
    \ingroup examples-network
    \brief Demonstrates how to create a client for a network service.

    This example uses QTcpSocket, and is intended to be run alongside the
    \l{fortuneserver}{Fortune Server} example or
    the \l{threadedfortuneserver}{Threaded Fortune Server} example.

    \image fortuneclient-example.png Screenshot of the Fortune Client example

    This example uses a simple QDataStream-based data transfer protocol to
    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 QString which contains the fortune text.

    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

    In this example, we will demonstrate the asynchronous approach. The
    \l{blockingfortuneclient}{Blocking Fortune Client} example
    illustrates the synchronous approach.

    Our class contains some data and a few private slots:

    \snippet fortuneclient/client.h 0

    Other than the widgets that make up the GUI, the data members include a
    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
    socket:

    \snippet fortuneclient/client.cpp 0
    \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_6_5 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::errorOccurred(), which we will use to catch any connection errors:

    \dots
    \snippet fortuneclient/client.cpp 3
    \dots
    \snippet fortuneclient/client.cpp 5

    Clicking the \uicontrol{Get Fortune} button will invoke the \c
    requestNewFortune() slot:

    \snippet fortuneclient/client.cpp 6

    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
    fortune server by calling QTcpSocket::connectToHost(), passing the
    hostname and port from the user interface as arguments.

    As a result of calling \l{QTcpSocket::connectToHost()}{connectToHost()},
    one of two things can happen:

    \list
    \li \e{The connection is established.} In this case, the server will send us a
    fortune. QTcpSocket will emit \l{QTcpSocket::readyRead()}{readyRead()}
    every time it receives a block of data.

    \li \e{An error occurs.} We need to inform the user if the connection
    failed or was broken. In this case, QTcpSocket will emit
    \l{QTcpSocket::errorOccurred()}{errorOccurred()}, and \c Client::displayError() will be
    called.
    \endlist

    Let's go through the \l{QTcpSocket::errorOccurred()}{errorOccurred()} case first:

    \snippet fortuneclient/client.cpp 13

    We pop up all errors in a dialog using
    QMessageBox::information(). QTcpSocket::RemoteHostClosedError is silently
    ignored, because the fortune server protocol ends with the server closing
    the connection.

    Now for the \l{QTcpSocket::readyRead()}{readyRead()} alternative. This
    signal is connected to \c Client::readFortune():

    \snippet fortuneclient/client.cpp 8

    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.

    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 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}, {Blocking Fortune Client}
*/