mirror of https://github.com/qt/qtgrpc.git
Doc: Review QtGRPC chat example
As part of reviewing the application examples and following a structure, this change covers: -Language changes -Follows the strucutre -Includes \sa link to All Qt Examples Fixes: QTBUG-137964 Pick-to: 6.10 6.9 6.8 Change-Id: Ic14d7042e6db277c1584b69ba46f7748f53642ab Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
This commit is contained in:
parent
b2d9291983
commit
49c0107975
|
|
@ -15,21 +15,40 @@
|
||||||
the \e ChatRoom, such as text messages, images, user activity or any other
|
the \e ChatRoom, such as text messages, images, user activity or any other
|
||||||
files from their disk with all other participants.
|
files from their disk with all other participants.
|
||||||
|
|
||||||
\inlineimage chat_room.webp
|
\inlineimage {chat_room.webp} {Mobile chat window}
|
||||||
\inlineimage chat_login.webp
|
\inlineimage {chat_login.webp} {Login window}
|
||||||
|
|
||||||
Some key topics covered in this example are:
|
\section1 Running the example
|
||||||
|
|
||||||
\list
|
\list
|
||||||
\li Communication through long-lived \l{QGrpcBidiStream}s.
|
\li Ensure that the \c{qtgrpc_chat_server} is running and successfully
|
||||||
|
listening.
|
||||||
|
\li If you are on the same machine as the server, the default
|
||||||
|
\c{localhost} address should suffice when running the
|
||||||
|
\c{qtgrpc_chat_client}. If you are using a device other than the
|
||||||
|
one hosting the server, specify the correct IP address of the host
|
||||||
|
running the server in the Settings dialog.
|
||||||
|
\li Ensure that the \c{GRPC_CHAT_USE_EMOJI_FONT} CMake option is
|
||||||
|
enabled on the client to build with a smooth emoji experience 🚀.
|
||||||
|
\endlist
|
||||||
|
|
||||||
|
\image {chat_settings.webp} {Connection settings window img}
|
||||||
|
|
||||||
|
To run the example from \l{\QC Documentation}{Qt Creator}, open the
|
||||||
|
\uicontrol Welcome mode and select the example from \uicontrol Examples.
|
||||||
|
For more information, see \l{\QC: Tutorial: Build and run}.
|
||||||
|
|
||||||
|
\section1 Relevant modules and classes.
|
||||||
|
This example introduces the following Qt modules and classes.
|
||||||
|
|
||||||
|
\list
|
||||||
|
\li Communication through long-lived \l{QGrpcBidiStream}.
|
||||||
\li Using the QtGrpc client from a worker \l{QThread}{thread}.
|
\li Using the QtGrpc client from a worker \l{QThread}{thread}.
|
||||||
\li Using the QtProtobufQtCoreTypes module in the protobuf schema.
|
\li Using the QtProtobufQtCoreTypes module in the protobuf schema.
|
||||||
\li Secure communication through \l{Secure Sockets Layer (SSL) Classes}{SSL}.
|
\li Secure communication through \l{Secure Sockets Layer (SSL) Classes}{SSL}.
|
||||||
\li Visualizing QtProtobuf messages in a QML ListView.
|
\li Visualizing QtProtobuf messages in a QML ListView.
|
||||||
\endlist
|
\endlist
|
||||||
|
|
||||||
\note Make sure to read the prerequisites in \l{Running the Example}.
|
|
||||||
|
|
||||||
\section1 Protobuf Schema
|
\section1 Protobuf Schema
|
||||||
|
|
||||||
The Protobuf schema defines the structure of messages and services used in
|
The Protobuf schema defines the structure of messages and services used in
|
||||||
|
|
@ -56,7 +75,7 @@
|
||||||
through the \c{ChatRoom} streaming RPC. Every \c{ChatMessage} must include
|
through the \c{ChatRoom} streaming RPC. Every \c{ChatMessage} must include
|
||||||
a \c{username} and \c{timestamp} to identify the sender.
|
a \c{username} and \c{timestamp} to identify the sender.
|
||||||
|
|
||||||
We include the \c{QtCore/QtCore.proto} import to enable the types of the
|
You include the \c{QtCore/QtCore.proto} import to enable the types of the
|
||||||
QtProtobufQtCoreTypes module, allowing seamless conversion between
|
QtProtobufQtCoreTypes module, allowing seamless conversion between
|
||||||
QtCore-specific types and their Protobuf equivalents.
|
QtCore-specific types and their Protobuf equivalents.
|
||||||
|
|
||||||
|
|
@ -81,12 +100,12 @@
|
||||||
|
|
||||||
\snippet chat/server/main.cpp server-1
|
\snippet chat/server/main.cpp server-1
|
||||||
|
|
||||||
We declare the \c{QtGrpcChatService} class, which subclasses the
|
You declare the \c{QtGrpcChatService} class, which subclasses the
|
||||||
\c{CallbackService} of the generated \c{QtGrpcChat} service.
|
\c{CallbackService} of the generated \c{QtGrpcChat} service.
|
||||||
|
|
||||||
\snippet chat/server/main.cpp server-2
|
\snippet chat/server/main.cpp server-2
|
||||||
|
|
||||||
We override the virtual functions to implement the functionality for the
|
Also override the virtual functions to implement the functionality for the
|
||||||
two \gRPC methods provided by the service:
|
two \gRPC methods provided by the service:
|
||||||
|
|
||||||
\list
|
\list
|
||||||
|
|
@ -107,9 +126,9 @@
|
||||||
\snippet chat/server/main.cpp server-4
|
\snippet chat/server/main.cpp server-4
|
||||||
|
|
||||||
The \c{startSharedWrite} method is a member function of the
|
The \c{startSharedWrite} method is a member function of the
|
||||||
\c{ChatRoomReactor}. If the reactor (i.e. the client) is currently writing,
|
\c{ChatRoomReactor}. If the reactor is currently writing, the message is
|
||||||
the message is buffered in a queue. Otherwise, a write operation is
|
buffered in a queue. Otherwise, a write operation is initiated. There is a
|
||||||
initiated. There is a single and unique message shared between all clients.
|
single and unique message shared between all clients.
|
||||||
Each copy of the \c{response} message increases the \c{use_count}. Once all
|
Each copy of the \c{response} message increases the \c{use_count}. Once all
|
||||||
clients have finished writing the message, and its \c{use_count} drops to 0 its
|
clients have finished writing the message, and its \c{use_count} drops to 0 its
|
||||||
resources are freed.
|
resources are freed.
|
||||||
|
|
@ -141,15 +160,15 @@
|
||||||
|
|
||||||
\snippet chat/client/CMakeLists.txt client-setup-1
|
\snippet chat/client/CMakeLists.txt client-setup-1
|
||||||
|
|
||||||
First, we generate the source files from the Protobuf schema. Since the
|
First, generate the source files from the Protobuf schema. Since the
|
||||||
\c{qtgrpcchat.proto} file does not contain any \c{message} definitions,
|
\c{qtgrpcchat.proto} file does not contain any \c{message} definitions,
|
||||||
only \l{qt_add_grpc}{qtgrpcgen} generation is required. We also provide the
|
only \l{qt_add_grpc}{qtgrpcgen} generation is required. Also provide the
|
||||||
\c{PROTO_INCLUDES} of the \c{ProtobufQtCoreTypes} module to ensure the
|
\c{PROTO_INCLUDES} of the \c{ProtobufQtCoreTypes} module to ensure the
|
||||||
\c{"QtCore/QtCore.proto"} import is valid.
|
\c{"QtCore/QtCore.proto"} import is valid.
|
||||||
|
|
||||||
\snippet chat/client/CMakeLists.txt client-setup-2
|
\snippet chat/client/CMakeLists.txt client-setup-2
|
||||||
|
|
||||||
We ensure that the independent \c{qtgrpc_chat_client_proto} target is
|
Ensure that the independent \c{qtgrpc_chat_client_proto} target is
|
||||||
publicly linked against its dependencies, including the
|
publicly linked against its dependencies, including the
|
||||||
\c{ProtobufQtCoreTypes} module. The application target is then linked
|
\c{ProtobufQtCoreTypes} module. The application target is then linked
|
||||||
against this library.
|
against this library.
|
||||||
|
|
@ -185,7 +204,7 @@
|
||||||
\snippet chat/client/chatengine.cpp client-3
|
\snippet chat/client/chatengine.cpp client-3
|
||||||
\dots 0
|
\dots 0
|
||||||
|
|
||||||
In the \c{ChatEngine} constructor, we assign the \c{ClientWorker} to its
|
In the \c{ChatEngine} constructor, assign the \c{ClientWorker} to its
|
||||||
dedicated worker thread and continue handling and forwarding its signals to
|
dedicated worker thread and continue handling and forwarding its signals to
|
||||||
make them available on the QML side.
|
make them available on the QML side.
|
||||||
|
|
||||||
|
|
@ -198,12 +217,12 @@
|
||||||
own thread, it is important to use \l{QMetaObject::}{invokeMethod} to call
|
own thread, it is important to use \l{QMetaObject::}{invokeMethod} to call
|
||||||
its member functions safely.
|
its member functions safely.
|
||||||
|
|
||||||
In the \c{ClientWorker}, we check whether the client is uninitialized or if
|
In the \c{ClientWorker}, you check whether the client is uninitialized or if
|
||||||
the host URI has changed. If either condition is met, we call
|
the host URI has changed. If either condition is met, call
|
||||||
\c{initializeClient}, which creates a new QGrpcHttp2Channel. Since this
|
\c{initializeClient}, which creates a new QGrpcHttp2Channel. Since this
|
||||||
is an expensive operation, we minimize its occurrences.
|
is an expensive operation, minimize its occurrences.
|
||||||
|
|
||||||
To handle the \c{Register} RPC, we use the
|
To handle the \c{Register} RPC, use the
|
||||||
\l{QGrpcCallOptions::}{setDeadlineTimeout} option to guard against server
|
\l{QGrpcCallOptions::}{setDeadlineTimeout} option to guard against server
|
||||||
inactivity. It is generally recommended to set a deadline for unary RPCs.
|
inactivity. It is generally recommended to set a deadline for unary RPCs.
|
||||||
|
|
||||||
|
|
@ -211,7 +230,7 @@
|
||||||
\dots
|
\dots
|
||||||
\snippet chat/client/clientworker.cpp client-5b
|
\snippet chat/client/clientworker.cpp client-5b
|
||||||
|
|
||||||
When logging into the \c{ChatRoom}, we use the
|
When logging into the \c{ChatRoom},you can use the
|
||||||
\l{QGrpcCallOptions::}{setMetadata} option to provide user credentials, as
|
\l{QGrpcCallOptions::}{setMetadata} option to provide user credentials, as
|
||||||
required by the server for authentication. The actual call and connection
|
required by the server for authentication. The actual call and connection
|
||||||
setup are handled in the \c{connectStream} method.
|
setup are handled in the \c{connectStream} method.
|
||||||
|
|
@ -223,8 +242,8 @@
|
||||||
\snippet chat/client/clientworker.cpp client-6c
|
\snippet chat/client/clientworker.cpp client-6c
|
||||||
\dots
|
\dots
|
||||||
|
|
||||||
We implement basic reconnection logic in case the stream finishes abruptly
|
To implement basic reconnection logic in case the stream finishes abruptly
|
||||||
while we are still connected. This is done by simply calling
|
while you are still connected. This is done by simply calling
|
||||||
\c{connectStream} again with the \c{QGrpcCallOptions} from the initial
|
\c{connectStream} again with the \c{QGrpcCallOptions} from the initial
|
||||||
call. This ensures that all required connections are also updated.
|
call. This ensures that all required connections are also updated.
|
||||||
|
|
||||||
|
|
@ -233,7 +252,7 @@
|
||||||
mode can be triggered, e.g., by using the FileDialog or switching to
|
mode can be triggered, e.g., by using the FileDialog or switching to
|
||||||
another app. This mode shuts down network access, closing all active
|
another app. This mode shuts down network access, closing all active
|
||||||
QTcpSocket connections and causing the stream to be
|
QTcpSocket connections and causing the stream to be
|
||||||
\l{QGrpcBidiStream::}{finished}. We address this issue with the
|
\l{QGrpcBidiStream::}{finished}. You can address this issue with the
|
||||||
reconnection logic.
|
reconnection logic.
|
||||||
|
|
||||||
\snippet chat/client/clientworker.cpp client-6d
|
\snippet chat/client/clientworker.cpp client-6d
|
||||||
|
|
@ -244,7 +263,7 @@
|
||||||
|
|
||||||
When messages are received, the \c{ClientWorker} performs some
|
When messages are received, the \c{ClientWorker} performs some
|
||||||
pre-processing, such as saving the \c{FileMessage} content, so that the
|
pre-processing, such as saving the \c{FileMessage} content, so that the
|
||||||
\c{ChatEngine} only needs to focus on the models. We use the \c{ContentFields}
|
\c{ChatEngine} only needs to focus on the models. Use the \c{ContentFields}
|
||||||
enum to safely check the \c{oneof content} field of our ChatMessage sum type.
|
enum to safely check the \c{oneof content} field of our ChatMessage sum type.
|
||||||
|
|
||||||
\snippet chat/client/chatengine.cpp client-7a
|
\snippet chat/client/chatengine.cpp client-7a
|
||||||
|
|
@ -274,7 +293,7 @@
|
||||||
|
|
||||||
\snippet chat/client/Main.qml client-qml-2
|
\snippet chat/client/Main.qml client-qml-2
|
||||||
|
|
||||||
In \c{Main.qml}, we handle core signals emitted by the \c{ChatEngine}. Most
|
\c{Main.qml} handles core signals emitted by the \c{ChatEngine}. Most
|
||||||
of these signals are handled globally and are visualized in any state of
|
of these signals are handled globally and are visualized in any state of
|
||||||
the application.
|
the application.
|
||||||
|
|
||||||
|
|
@ -304,11 +323,11 @@
|
||||||
\snippet chat/client/ChatView.qml client-qml-4e
|
\snippet chat/client/ChatView.qml client-qml-4e
|
||||||
|
|
||||||
In \c{ChatView.qml}, the ListView displays messages in the \c{ChatRoom}.
|
In \c{ChatView.qml}, the ListView displays messages in the \c{ChatRoom}.
|
||||||
This is slightly more complex, as we need to handle the \c{ChatMessage} sum
|
This is slightly more complex, as you need to handle the \c{ChatMessage} sum
|
||||||
type conditionally.
|
type conditionally.
|
||||||
|
|
||||||
To handle this, we use a DelegateChooser, which allows us to select the
|
You can use a DelegateChooser, which allows us to select the appropriate
|
||||||
appropriate delegate based on the type of message. We use the default
|
delegate based on the type of message. Use the default
|
||||||
\c{whatThis} role in the model, which provides the message type for each
|
\c{whatThis} role in the model, which provides the message type for each
|
||||||
\c{ChatMessage} instance. The \c{DelegateBase} component then accesses the
|
\c{ChatMessage} instance. The \c{DelegateBase} component then accesses the
|
||||||
\c{display} role of the model, making the chatMessage data available for
|
\c{display} role of the model, making the chatMessage data available for
|
||||||
|
|
@ -356,14 +375,14 @@
|
||||||
pre-installed in the client's trust store like those of public CAs.
|
pre-installed in the client's trust store like those of public CAs.
|
||||||
\endlist
|
\endlist
|
||||||
|
|
||||||
We used \l{https://www.openssl.org/}{OpenSSL} to create these files and set
|
Use \l{https://www.openssl.org/}{OpenSSL} to create these files and set
|
||||||
up our \gRPC communication to use SSL/TLS.
|
up our \gRPC communication to use SSL/TLS.
|
||||||
|
|
||||||
\snippet chat/server/main.cpp server-ssl
|
\snippet chat/server/main.cpp server-ssl
|
||||||
|
|
||||||
We provide the \e{Private Key} and \e{Certificate} to the \gRPC server.
|
You provide the \e{Private Key} and \e{Certificate} to the \gRPC server.
|
||||||
With that, we construct the \c{SslServerCredentials} to enable TLS on the
|
With that, you can construct the \c{SslServerCredentials} to enable TLS on the
|
||||||
server-side. In addition to secure communication, we also allow unencrypted
|
server-side. In addition to secure communication, also allow unencrypted
|
||||||
access.
|
access.
|
||||||
|
|
||||||
The server listens on the following addresses:
|
The server listens on the following addresses:
|
||||||
|
|
@ -377,29 +396,13 @@
|
||||||
|
|
||||||
\snippet chat/client/clientworker.cpp client-ssl
|
\snippet chat/client/clientworker.cpp client-ssl
|
||||||
|
|
||||||
The client loads the \e{Root CA Certificate}, as we self-signed the CA.
|
The client loads the \e{Root CA Certificate}, as you self-signed the CA.
|
||||||
This certificate is used to create the QSslCertificate. It is important to
|
This certificate is used to create the QSslCertificate. It is important to
|
||||||
provide the \c{"h2"} protocol with
|
provide the \c{"h2"} protocol with
|
||||||
\l{QSslConfiguration::}{setAllowedNextProtocols}, as we are using HTTP/2.
|
\l{QSslConfiguration::}{setAllowedNextProtocols}, as you are using HTTP/2.
|
||||||
|
|
||||||
\section1 Running the example
|
\section1 Source files
|
||||||
|
|
||||||
\list
|
\sa {All Qt Examples}
|
||||||
\li Ensure that the \c{qtgrpc_chat_server} is running and successfully
|
|
||||||
listening.
|
|
||||||
\li If you are on the same machine as the server, the default
|
|
||||||
\c{localhost} address should suffice when running the
|
|
||||||
\c{qtgrpc_chat_client}. If you are using a device other than the
|
|
||||||
one hosting the server, specify the correct IP address of the host
|
|
||||||
running the server in the Settings dialog.
|
|
||||||
\li Ensure that the \c{GRPC_CHAT_USE_EMOJI_FONT} CMake option is
|
|
||||||
enabled on the client to build with a smooth emoji experience 🚀.
|
|
||||||
\endlist
|
|
||||||
|
|
||||||
\image chat_settings.webp
|
|
||||||
|
|
||||||
To run the example from \l{\QC Documentation}{Qt Creator}, open the
|
|
||||||
\uicontrol Welcome mode and select the example from \uicontrol Examples.
|
|
||||||
For more information, see \l{\QC: Tutorial: Build and run}.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue