Qt GRPC 服务客户端方法

gRPC 允许您定义四种类型的服务方法

  • 一元调用,客户端向服务器发送单个请求并收到单个响应
    rpc PingPong (Ping) returns (Pong);
  • 服务器流,客户端向服务器发送单个请求并收到一个或多个响应
    rpc PingSeveralPong (Ping) returns (stream Pong);
  • 客户端流,客户端向服务器发送一个或多个请求并收到单个响应
    rpc SeveralPingPong (stream Ping) returns (Pong);
  • 双向流,客户端向服务器发送一个或多个请求并收到一个或多个响应
    rpc SeveralPingSeveralPong (stream Ping) returns (stream Pong);

    请注意,响应的数量可能不与请求的数量一致,也不与请求和响应的顺序一致。这由应用程序的业务逻辑控制。

gRPC 通信始终从客户端开始,在服务器端结束。客户端通过向服务器发送第一条消息来启动通信。服务器通过回复状态码来结束任何类型的通信。

要使用 Qt GRPC C++ API,首先为您的项目定义 pingpong.proto 模式

syntax = "proto3";

package ping.pong;

message Ping {
    uint64 time = 1;
    sint32 num = 2;
}

message Pong {
    uint64 time = 1;
    sint32 num = 2;
}

service PingPongService {
    // Unary call
    rpc PingPong (Ping) returns (Pong);

    // Server stream
    rpc PingSeveralPong (Ping) returns (stream Pong);

    // Client stream
    rpc SeveralPingPong (stream Ping) returns (Pong);

    // Bidirectional stream
    rpc SeveralPingSeveralPong (stream Ping) returns (stream Pong);
}

使用上述模式分割以及 Qt GRPC CMake API 生成 C++ 客户端代码

find_package(Qt6 COMPONENTS Protobuf Grpc)

qt_add_executable(pingpong ...)

qt_add_protobuf(pingpong PROTO_FILES pingpong.proto)
qt_add_grpc(pingpong CLIENT PROTO_FILES pingpong.proto)

生成的 protobuf 消息和客户端 gRPC 代码都将添加到 pingpong CMake 目标中。

使用 Qt GRPC 中的唯一调用

让我们从一个最简单的通信场景开始 - 一个一元的 gRPC 调用。在这种 RPC 类型中,客户端向服务器发送单个请求消息,并从服务器接收单个响应消息。在服务器发送状态码后,通信结束。

对于唯一调用,qtgrpcgen 工具会生成两种不同的异步方法

namespace ping::pong {
namespace PingPongService {

class Client : public QAbstractGrpcClient {
    Q_OBJECT
public:
    std::shared_ptr<QGrpcCallReply> PingPong(const ping::pong::Ping &arg,
                                            const QGrpcCallOptions &options = {});
    Q_INVOKABLE void PingPong(const ping::pong::Ping &arg, const QObject *context,
                            const std::function<void(std::shared_ptr<QGrpcCallReply>)> &callback,
                            const QGrpcCallOptions &options = {});
    ...
};
} // namespace PingPongService
} // namespace ping::pong

使用 QGrpcCallReply 处理调用回复

第一个变体返回 QGrpcCallReply gRPC 操作。QGrpcCallReply 读取从服务器接收的消息,并获取有关错误或调用结束的通知。

在创建 PingPongService::Client 并将其附加到 QGrpcHttp2Channel 之后,调用 PingPong 方法

qint64 requestTime = QDateTime::currentMSecsSinceEpoch();
ping::pong::Ping request;
request.setTime(requestTime);

auto reply = cl.PingPong(request,{});
QObject::connect(reply.get(), &QGrpcCallReply::finished, reply.get(),
                 [requestTime, replyPtr = reply.get()]() {
                     auto response = replyPtr->read<ping::pong::Pong>();
                     qDebug() << "Ping-Pong time difference" << response.time() - requestTime;
                 });

QObject::connect(reply.get(), &QGrpcCallReply::errorOccurred, stream.get()
                 [](const QGrpcStatus &status) {
                     qDebug() << "Error occurred: " << status.code() << status.message();
                 });

在服务器响应请求后,将发出 QGrpcCallReply::finished 信号。该 reply 对象包含从服务器接收的原始响应数据,可以使用 QGrpcCallReply::read 方法反序列化为 ping::pong::Pong protobuf 消息。

如果服务器没有响应或请求在服务器端引起错误,将发出 QGrpcCallReply::errorOccurred 信号,并带有相应的 状态码。如果服务器用 QGrpcStatus::Ok 状态码回答,则不会发出 QGrpcCallReply::errorOccurred 信号。

使用回调处理呼叫应答

重载的函数与返回 QGrpcCallReply 的函数相似,但该函数不是返回应答,而是将应答作为参数传递给用于调用的回调函数

...
cl.PingPong(request, &a, [requestTime](std::shared_ptr<QGrpcCallReply> reply) {
    auto response = reply->read<ping::pong::Pong>();
    qDebug() << "Ping and Pong time difference" << response.time() - requestTime;
});

此变体隐式连接到 QGrpcCallReply::finished 信号,但您不能使用 QGrpcOperation::cancel 函数取消调用,要获取关于发生错误的信息,您需要订阅通用的 QAbstractGrpcClient::errorOccurred 信号。

在 Qt GRPC 中使用服务器流

服务器流扩展了单一调用场景,并允许服务器多次对客户端请求进行响应。一旦服务器发送状态代码,通信就结束。

对于服务器流,qtgrpcgen 工具生成了返回 QGrpcServerStream 指针的方法

std::shared_ptr<QGrpcServerStream> streamPingSeveralPong(const ping::pong::Ping &arg,
                                                         const QGrpcCallOptions &options = {});

QGrpcServerStreamQGrpcCallReply 类似,但服务器响应接收到时会发出 QGrpcServerStream::messageReceived

QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, stream.get(),
                 [streamPtr = stream.get(), requestTime]() {
                     auto response = streamPtr->read<ping::pong::Pong>();
                     qDebug() << "Ping-Pong next response time difference"
                              << response.time() - requestTime;
                 });

QObject::connect(stream.get(), &QGrpcServerStream::errorOccurred, stream.get()
                 [](const QGrpcStatus &status) {
                     qDebug() << "Error occurred: " << status.code() << status.message();
                 });

QObject::connect(stream.get(), &QGrpcServerStream::finished, stream.get(),
                 []{
                     qDebug() << "Bye";
                 });

注意:接收来自服务器的新的消息时,QGrpcServerStream 会覆盖内部缓冲区。在服务器 finished 通信后,您只能读取从服务器接收到的最后一条消息。

在 Qt GRPC 中使用客户端流

客户端流扩展了单一调用场景,并允许客户端发送多个请求。服务器在结束通信之前只响应一次。

对于客户端流,qtgrpcgen 工具生成了返回 QGrpcClientStream 指针的方法

std::shared_ptr<QGrpcClientStream> streamSeveralPingPong(const ping::pong::Ping &arg,
                                                         const QGrpcCallOptions &options = {});

要向服务器发送多个请求,请使用 QGrpcClientStream::sendMessage 方法

auto stream = cl.streamSeveralPingPong(request);

QTimer timer;
QObject::connect(&timer, &QTimer::timeout, stream.get(),
                 [streamPtr = stream.get()](){
                     ping::pong::Ping request;
                     request.setTime(QDateTime::currentMSecsSinceEpoch());
                     streamPtr->sendMessage(request);
                 });

QObject::connect(stream.get(), &QGrpcServerStream::finished, stream.get(),
                 [streamPtr = stream.get(), &timer]{
                     auto response = streamPtr->read<ping::pong::Pong>();
                     qDebug() << "Slowest Ping time: " << response.time();
                     timer.stop();
                 });

QObject::connect(stream.get(), &QGrpcServerStream::errorOccurred, stream.get()
                 [&timer](const QGrpcStatus &status){
                     qDebug() << "Error occurred: " << status.code() << status.message();
                     timer.stop();
                 });

timer.start(1000);
return a.exec();

在客户端发送足够多的 Ping 请求后,服务器会以包含最慢 Ping 时间的 Pong 进行响应。

在 Qt GRPC 中使用双向流

双向流结合了服务器和客户端流的功能。生成的方法返回指向 QGrpcBidirStream 的指针,该 API 提供服务器和客户端流的接口

std::shared_ptr<QGrpcBidirStream> streamSeveralPingSeveralPong(const ping::pong::Ping &arg,
                                                               const QGrpcCallOptions &options = {});

使用双向流组织双方的通信,而不中断连接会话

auto stream = cl.streamSeveralPingSeveralPong(request);

qint64 maxPingPongTime = 0;
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, stream.get(),
                 [streamPtr = stream.get(), &requestTime](){
                     requestTime = QDateTime::currentMSecsSinceEpoch();
                     ping::pong::Ping request;
                     request.setTime(requestTime);
                     streamPtr->sendMessage(request);
                 });

QObject::connect(stream.get(), &QGrpcBidirStream::messageReceived, stream.get(),
                 [streamPtr = stream.get(), &timer, &maxPingPongTime, &requestTime]{
                     auto response = streamPtr->read<ping::pong::Pong>();
                     maxPingPongTime = std::max(maxPingPongTime, response.time() - requestTime);
                 });

QObject::connect(stream.get(), &QGrpcBidirStream::finished, stream.get(),
                 [streamPtr = stream.get(), &timer, &maxPingPongTime]{
                     qDebug() << "Maximum Ping-Pong time: " << maxPingPongTime;
                     timer.stop();
                 });

QObject::connect(stream.get(), &QGrpcBidirStream::errorOccurred, stream.get(),
                 [&timer](const QGrpcStatus &status){
                     qDebug() << "Error occurred: " << status.code() << status.message();
                     timer.stop();
                 });

timer.start(1000);

每次客户端发送 Ping 请求时,服务器都会以 Pong 消息进行响应。直到服务器向客户端发送状态代码结束通信,才会评估 Ping-Pong 时间最大值。

注意:QGrpcBidirStream 接收来自服务器的新的消息时会覆盖内部缓冲区。在服务器 finished 通信后,您只能读取从服务器接收到的最后一条消息。

© 2024 The Qt Company Ltd. 本内包含的文档贡献的版权归其各自的所有者所有。本提供的文档根据自由软件基金会的出版物《GNU 自由文档许可证》第 1.3 版的条款进行许可。Qt 以及相应的徽标是芬兰和/或其他国家/地区的 The Qt Company Ltd. 的商标。所有其他商标是其各自所有者的财产。