Qt GRPCサービスのクライアントメソッド

gRPCでは、4種類のサービスメソッドを定義することができます:

  • 単項呼び出し:クライアントがサーバに単一のリクエストを送信し、単一のレスポンスを返します:
    rpc PingPong (Ping) returns (Pong);
  • サーバーストリーム:クライアントがサーバーに1つのリクエストを送信し、1つ以上のレスポンスを返します:
    rpc PingSeveralPong (Ping) returns (stream Pong);
  • クライアントストリーム:クライアントがサーバーに1つ以上のリクエストを送り、1つのレスポンスを返す:
    rpc SeveralPingPong (stream Ping) returns (Pong);
  • 双方向ストリーム:クライアントが1つ以上のリクエストをサーバーに送り、1つ以上のレスポンスを返す:
    rpc SeveralPingSeveralPong (stream Ping) returns (stream Pong);

    レスポンスの数はリクエストの数と一致しないかもしれないし、リクエストとレスポンスの順序も一致しないかもしれないことに注意。これはアプリケーションのビジネスロジックによって制御される。

gRPC通信は常にクライアント側から始まり、サーバ側で終わります。クライアントは最初のメッセージをサーバーに送信することで通信を開始します。サーバは、status code で応答することで、任意のタイプの通信を終了します。

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ツールは2つの代替非同期メソッドを生成します:

namespace ping::pong {
namespace PingPongService {

class Client : public QGrpcClientBase {
    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()]() {
                     if (const auto response = replyPtr->read<ping::pong::Pong>())
                        qDebug() << "Ping-Pong time difference" << response->time() - requestTime;
                    qDebug() << "Failed deserialization";
                 });

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シグナルが、対応するstatus code 。サーバーがQtGrpc::StatusCode::Ok コードで応答した場合、QGrpcCallReply::errorOccurred シグナルは発行されない。

コールバックを使用したコールリプライ処理

オーバーロードされた関数は、QGrpcCallReply を返す関数と似ているが、リプライを返す代わりに、呼び出しで使用されるコールバック関数の引数として渡す:

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

この変種は、QGrpcCallReply::finished シグナルに暗黙的に接続しますが、QGrpcOperation::cancel 関数を使用して呼び出しをキャンセルすることはできません。

Qt GRPC でのサーバーストリームの使用

サーバーストリームは単項呼び出しシナリオを拡張し、サーバーがクライアントリクエストに複数回応答できるようにします。サーバーがステータスコードを送信すると、通信は終了します。

サーバ・ストリームでは、qtgrpcgen ツールがQGrpcServerStream へのポインタを返すメソッドを生成します:

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

QGrpcServerStream は と似ていますが、サーバー応答を受信すると を発行します。QGrpcCallReply QGrpcServerStream::messageReceived

QObject::connect(stream.get(), &QGrpcServerStream::messageReceived, stream.get(),
                 [streamPtr = stream.get(), requestTime]() {
                     if (const 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 でのクライアント・ストリームの使用

クライアントストリームは、単項呼び出しのシナリオを拡張し、クライアントが複数のリクエストを送信できるようにします。サーバーは、通信を終了する前に 1 回だけ応答します。

サーバーストリームでは、qtgrpcgen ツールが QGrpcClientStream へのポインタを返すメソッドを生成します:

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

サーバーに複数のリクエストを送信するには、QGrpcClientStream::writeMessage メソッドを使用します:

auto stream = cl.severalPingPong(request);

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

QObject::connect(stream.get(), &QGrpcServerStream::finished, stream.get(),
                 [streamPtr = stream.get(), &timer]{
                     if (const 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 での双方向ストリームの使用

双方向ストリームは、サーバー・ストリームとクライアント・ストリームの機能を兼ね備えています。生成されたメソッドは、QGrpcBidiStream へのポインタを返します。このポインタは、サーバー・ストリームとクライアント・ストリームの両方の API を提供します:

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

双方向ストリームを使用すると、接続セッションを切断することなく、双方向通信を整理できます:

auto stream = cl.severalPingSeveralPong(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->writeMessage(request);
                 });

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

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

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

timer.start(1000);

クライアントがPing リクエストを送信するたびに、サーバーはPong メッセージで応答する。サーバーがクライアントにステータスコードを送信して通信を終了するまで、 最大Ping-Pong時間が評価される。

注意: QGrpcBidiStream は、サーバーから新しいメッセージを受信するとき、内部バッファをオーバーライドする。サーバfinished が通信を終了した後、サーバから最後に受信したメッセージのみを読むことができます。

本ドキュメントに含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。