Qt GRPC クライアントガイド。
サービスメソッド
サービスメソッドは gRPC™では、クライアントとサーバー間の通信を指定するために、protobufスキーマでサービスメソッドを定義することができます。protobufコンパイラのprotoc
、これらの定義に基づいて必要なサーバーとクライアントのインターフェースを生成できます。gRPC 、4種類のサービスメソッドをサポートしています:
- 単項呼び出し- クライアントは単一のリクエストを送信し、単一のレスポンスを受信する。
rpc UnaryCall (Request) returns (Response);
対応するクライアントハンドラはQGrpcCallReply である。
- サーバー・ストリーミング- クライアントは一つのリクエストを送り、複数のレスポンスを受け取る。
rpc ServerStreaming (Request) returns (stream Response);
対応するクライアントハンドラはQGrpcServerStream です。
- クライアント ストリーミング- クライアントは複数のリクエストを送信し、単一の応答を受信する。
rpc ClientStreaming (stream Request) returns (Response);
対応するクライアントハンドラはQGrpcClientStream です。
- 双方向ストリーミング- クライアントとサーバーが複数のメッセージを交換する。
rpc BidirectionalStreaming (stream Request) returns (stream Response);
対応するクライアントハンドラはQGrpcBidiStream です。
gRPC 通信は常にクライアントから開始され、クライアントは最初のメッセージをサーバーに送信してリモート・プロシージャ・コール(RPC)を開始します。その後、サーバは を返すことで、あらゆるタイプの通信を終了します。StatusCode
すべてのクライアント RPC ハンドラは、共有機能を提供するQGrpcOperation クラスから派生します。RPC は非同期のため、Qt のSignals & Slotsメカニズムで管理されます。
すべての RPC ハンドラに共通する重要なシグナ ルは、RPC の完了を示すfinished です。ハンドラは、その存続期間中に一度だけこのシグナルを発信します。このシグナルは、RPC の成否に関する追加情報を提供するQGrpcStatus に対応します。
また、着信メッセージのためのmessageReceived 、サーバへのメッセー ジ送信のためのwriteMessage 、クライアント側通信を終了するためのwritesDone など、操作に特化した機能もあります。以下の表に、RPC クライアント・ハンドラのサポート機能の概要を示します:
機能 | QGrpcCallReply | QGrpcServerStream | QGrpcClientStream | QGrpcBidiStream |
---|---|---|---|---|
finished | 機能 ✓ (read 最終応答) | ✓ | ✓ (read 最終レスポンス) | ✓ |
messageReceived | ✗ | ✓ | ✗ | ✓ |
writeMessage | ✗ | ✗ | ✓ | ✓ |
writesDone | ✗ | ✗ | ✓ | ✓ |
スタート
Qt GRPC C++ APIを使用するには、まずすでに利用可能なプロトブフスキーマを使用するか、独自のスキーマを定義します。ここでは例としてclientguide.proto
ファイルを使用します:
syntax = "proto3"; package client.guide; // enclosing namespace message Request { int64 time = 1; sint32 num = 2; } message Response { int64 time = 1; sint32 num = 2; } service ClientGuideService { rpc UnaryCall (Request) returns (Response); rpc ServerStreaming (Request) returns (stream Response); rpc ClientStreaming (stream Request) returns (Response); rpc BidirectionalStreaming (stream Request) returns (stream Response); }
この.protoファイルを C++ のQt GRPC クライアントに使うには、protoc
コンパイラと Qt ジェネレータプラグインを実行する必要があります。幸いなことに、Qtはqt_add_grpcと qt_add_protobufCMake関数を提供しており、このプロセスを効率化することができます。
set(proto_files "${CMAKE_CURRENT_LIST_DIR}/../proto/clientguide.proto") find_package(Qt6 COMPONENTS Protobuf Grpc) qt_standard_project_setup(REQUIRES 6.8) qt_add_executable(clientguide_client main.cpp) # Using the executable as input target will append the generated files to it. qt_add_protobuf(clientguide_client PROTO_FILES ${proto_files} ) qt_add_grpc(clientguide_client CLIENT PROTO_FILES ${proto_files} ) target_link_libraries(clientguide_client PRIVATE Qt6::Protobuf Qt6::Grpc)
この結果、2つのヘッダーファイルが現在のビルドディレクトリに生成されます:
- clientguide.qpb.h:qtprotobufgenによって生成される。スキーマから
Request
、Response
protobufメッセージを宣言します。 - clientguide_client.grpc.qpb.h:qtgrpcgenによって生成されます。スキーマから
ClientGuideService
を実装するgRPC サーバーのメソッドを呼び出すためのクライアント インターフェイスを宣言します。
以下のクライアント・インターフェイスが生成される:
namespace client::guide { namespace ClientGuideService { class Client : public QGrpcClientBase { ... std::unique_ptr<QGrpcCallReply> UnaryCall(const client::guide::Request &arg); std::unique_ptr<QGrpcServerStream> ServerStreaming(const client::guide::Request &arg); std::unique_ptr<QGrpcClientStream> ClientStreaming(const client::guide::Request &arg); std::unique_ptr<QGrpcBidiStream> BidirectionalStreaming(const client::guide::Request &arg); ... }; } // namespace ClientGuideService } // namespace client::guide
注: Client
インタフェースが返す一意の RPC ハンドラは、少なくともfinished シグナルが発せられるまで、ユーザが責任をもって管理する必要があります。このシグナルを受け取った後、ハンドラは安全に再割り当てまたは破棄することができる。
サーバー・セットアップ
ClientGuideService
、サーバーの実装は単純なアプローチに従う。リクエストメッセージのtime
フィールドを検証し、時刻が未来であればINVALID_ARGUMENT
ステータスコードを返す:
const auto time = now(); if (request->time() > time) return { grpc::StatusCode::INVALID_ARGUMENT, "Request time is in the future!" };
さらに、サーバーはすべての応答メッセージに現在時刻を設定する:
response->set_num(request->num()); response->set_time(time); return grpc::Status::OK;
有効なtime
、サービスメソッドは次のように動作する:
UnaryCall
:リクエストのnum
フィールドで応答する。ServerStreaming
:リクエストメッセージにマッチするnum
。ClientStreaming
:リクエストメッセージの数を数え、この数をnum
とする。BidirectionalStreaming
:受信した各リクエストメッセージのnum
フィールドで即座に応答する。
クライアントの設定
生成されたヘッダーファイルを含めることから始める:
#include "clientguide.qpb.h" #include "clientguide_client.grpc.qpb.h"
この例では、すべての通信を管理するためにClientGuide
。まず、gRPC のすべての通信のバックボーンであるチャネルを設定します。
auto channel = std::make_shared<QGrpcHttp2Channel>( QUrl("http://localhost:50056") /* without channel options. */ ); ClientGuide clientGuide(channel);
Qt GRPC ライブラリはQGrpcHttp2Channel を提供しており、生成されたクライアント・インターフェースにattach を渡すことができます:
explicit ClientGuide(std::shared_ptr<QAbstractGrpcChannel> channel) { m_client.attachChannel(std::move(channel)); }
このセットアップでは、クライアントはトランスポート・プロトコルとしてTCPを使用してHTTP/2上で通信する。通信は暗号化されません(つまりSSL/TLSの設定なし)。
リクエストメッセージの作成
リクエストメッセージを作成するためのシンプルなラッパーを以下に示します:
static guide::Request createRequest(int32_t num, bool fail = false) { guide::Request request; request.setNum(num); // The server-side logic fails the RPC if the time is in the future. request.setTime(fail ? std::numeric_limits<int64_t>::max() : QDateTime::currentMSecsSinceEpoch()); return request; }
この関数は整数とオプションで真偽値を受け取ります。デフォルトでは、この関数のメッセージは現在時刻を使うので、サーバーのロジッ クはそれを受け入れるはずです。しかし、fail
をtrue
に設定して呼び出されると、サーバーが拒否するメッセージを生成します。
シングルショットRPC
RPC クライアント・ハンドラを扱うには、さまざまなパラダイ ムがあります。具体的には、RPC ハンドラを包含するクラスのメンバとするクラス・ベースの設計を選択することもできますし、finished シグナルを通じて RPC ハンドラのライフタイムを管理することもできます。
シングル・ショット・パラダイムを適用する場合、 覚えておくべき重要なことが 2 つあります。以下のコードは、単項式呼び出しに対す る方法を示していますが、他のどの RPC タイプに対 しても同じです。
std::unique_ptr<QGrpcCallReply> reply = m_client.UnaryCall(requestMessage); const auto *replyPtr = reply.get(); // 1 QObject::connect( replyPtr, &QGrpcCallReply::finished, replyPtr, [reply = std::move(reply)](const QGrpcStatus &status) { ... }, Qt::SingleShotConnection // 2 );
- 1: ラムダ内でユニークな RPC オブジェクトのライフタイ ムを管理しているため、ラムダのキャプチャに移動すると、
get()
やその他のメンバ関数が無効になります。そのため、ポインタのアドレスをコピーしてから移動する必要があります。 - 2:finished シグナルは一度しか発信されないため、真のシングルショット接続となります。この接続をSingleShotConnection としてマークすることが重要である!そうしないと、
reply
のキャプチャが破棄されず、発見しにくい隠れたメモリー・リークにつながります。
connect
呼び出しの引数SingleShotConnection は、スロットファンクター(ラムダ)が発行された後に破棄され、キャプチャを含むスロットに関連するリソースが解放されることを保証します。
リモート手続き呼び出し
単項呼び出し
単項呼び出しは、finished シグナルのみを処理すればよい。このシグナルが発せられると、RPCのstatus をチェックし、成功したかどうかを判断することができる。成功した場合、サーバーからの単一で最終的な応答をread 。
この例では、シングルショットパラダイムを使用します。シングルショット RPC のセクションをよく読んでください。
voidunaryCall(constguide::Request&request) { std::unique_ptr<QGrpcCallReply> reply=m_client.UnaryCall(request);const auto *replyPtr =reply.get(); QObject::connect( replyPtr, &)QGrpcCallReply::finished,replyPtr,[reply =std::move(reply)](constQGrpcStatus(&status) {if(status.isOk()) {if(const autoresponse= reply->read<guide::Response>()) qDebug() << "Client (UnaryCall) finished, received:" << *response; その他 qDebug("Client (UnaryCall) deserialization failed"); }else{ qDebug() << "Client (UnaryCall) failed:" << status; }}, Qt::SingleShotConnection); }
この関数は、生成されたクライアント・インターフェースm_client
のメンバ関数UnaryCall
を呼び出して RPC を開始する。ライフタイムはfinished シグナルによってのみ管理される。
コードの実行
main
では、この関数を単純に 3 回呼び出し、2 回目の呼び出 しを失敗させています:
clientGuide.unaryCall(ClientGuide::createRequest(1)); clientGuide.unaryCall(ClientGuide::createRequest(2, true)); // fail the RPC clientGuide.unaryCall(ClientGuide::createRequest(3));
これを実行すると、次のような出力が得られる:
Welcome to the clientguide! Starting the server process ... Server listening on: localhost:50056 Server (UnaryCall): Request( time: 1733498584776, num: 1 ) Server (UnaryCall): Request( time: 9223372036854775807, num: 2 ) Server (UnaryCall): Request( time: 1733498584776, num: 3 ) Client (UnaryCall) finished, received: Response( time: 1733498584778257 , num: 1 ) Client (UnaryCall) failed: QGrpcStatus( code: QtGrpc::StatusCode::InvalidArgument, message: "Request time is in the future!" ) Client (UnaryCall) finished, received: Response( time: 1733498584778409 , num: 3 )
サーバーが3つのメッセージを受信しているのがわかる。クライアント側では、最初と最後の呼び出しはOk ステータスコードを返したが、2番目のメッセージはメッセージの時間が未来にあるため、InvalidArgument ステータスコードで失敗した。
サーバー・ストリーミング
サーバー・ストリームでは、クライアントが最初のリクエストを送信し、サーバーが1つ以上のメッセージで応答します。finished シグナルに加えて、messageReceived シグナルも処理する必要があります。
この例では、シングルショットパラダイムを使用して、ストリーミング RPC ライフサイクルを管理します。シングルショット RPC のセクションを注意深く読んでください。
他の RPC と同様に、まずfinished シグナルに接続します:
voidserverStreaming(constguide::Request&initialRequest) { std::unique_ptr<QGrpcServerStream> stream=m_client.ServerStreaming(initialRequest);const auto *streamPtr =stream.get(); QObject::connect( streamPtr, &)QGrpcServerStream::finished,streamPtr,[stream =std::move(stream)](constQGrpcStatus&status) {if(status.isOk()) qDebug("Client (ServerStreaming) finished"); その他 qDebug() << "Client (ServerStreaming) failed:" << status; }, Qt::SingleShotConnection);
サーバー・メッセージを処理するために、messageReceived シグナルに接続し、シグナルが発せられるとread レスポンスを返す。
QObject::connect(streamPtr, &)QGrpcServerStream::messageReceived,streamPtr, [streamPtr]{if(const autoresponse= streamPtr->read<guide::Response>()) qDebug() << "Client (ServerStream) received:" << *response; その他 qDebug("Client (ServerStream) deserialization failed"); }); }
コードの実行
サーバーロジックは、最初のリクエストで受け取った金額をクライアントにストリームバックします。このようなリクエストを作成し、関数を呼び出します。
clientGuide.serverStreaming(ClientGuide::createRequest(3));
サーバーストリーミングを実行すると、次のような出力が考えられます:
Welcome to the clientguide! Starting the server process ... Server listening on: localhost:50056 Server (ServerStreaming): Request( time: 1733504435800, num: 3 ) Client (ServerStream) received: Response( time: 1733504435801724 , num: 0 ) Client (ServerStream) received: Response( time: 1733504435801871 , num: 1 ) Client (ServerStream) received: Response( time: 1733504435801913 , num: 2 ) Client (ServerStreaming) finished
サーバーが起動すると、num値3のリクエストを受信し、3つのResponse
メッセージで応答してから通信を完了する。
クライアント・ストリーミング
クライアント・ストリームでは、クライアントは1つ以上のリクエストを送信し、サーバーは1つの最終レスポンスで応答する。finished シグナルを処理する必要があり、writeMessage 関数を使用してメッセージを送信できます。その後、writesDone 関数を使用して、クライアントが書き込みを終了し、これ以上メッセージを送信しないことを示すことができます。
ストリーミング RPC との対話にはクラス・ベースのアプローチを使用し、ハンドラをクラスのメンバとして組み込みます。他の RPC と同様に、finished シグナルに接続します:
voidclientStreaming(constguide::Request&initialRequest) { m_clientStream=m_client.ClientStreaming(initialRequest);for(int32_t i= 1; i< 3;++i) m_clientStream->writeMessage(createRequest(initialRequest.num()+i)); m_clientStream->writesDone(); QObject::connect(m_clientStream.get())、&。QGrpcClientStream::finished,m_clientStream.get(),[this](constQGrpcStatus(&status) {if(status.isOk()) {if(const autoresponse= m_clientStream->read<guide::Response>()) qDebug() << "Client (ClientStreaming) finished, received:" << *レスポンス; m_clientStream.reset(); }else{ {. qDebug() << "Client (ClientStreaming) failed:" << status; qDebug("Restarting the client stream"); clientStreaming(createRequest(0)); } }); }
この関数は、初期メッセージでクライアント・ストリームを開始します。その後、writesDone を呼び出して通信の終了を通知する前に、さらに 2 つのメッセージを書き続けます。ストリーミング RPC が成功した場合、read サーバからの最終応答とreset
RPC オブジェクトを返します。RPC が失敗した場合は、m_clientStream
メンバを上書きし、finished シグナルを再接続する同じ関数を呼び出して再試行します。必要な接続が失われるため、ラムダ内でm_clientStream
メンバを単純に再割り当てすることはできません。
コードの実行
main
で、clientStreaming
関数を失敗メッセージとともに呼び出すと、RPC 失敗がトリガされ、再試行ロジックが実行されます。
clientGuide.clientStreaming(ClientGuide::createRequest(0, true)); // fail the RPC
クライアント・ストリーミングを実行する と、次のような出力が得られます:
Welcome to the clientguide! Starting the server process ... Server listening on: localhost:50056 Server (ClientStreaming): Request( time: 9223372036854775807, num: 0 ) Client (ClientStreaming) failed: QGrpcStatus( code: QtGrpc::StatusCode::InvalidArgument, message: "Request time is in the future!" ) Restarting the client stream Server (ClientStreaming): Request( time: 1733912946696, num: 0 ) Server (ClientStreaming): Request( time: 1733912946697, num: 1 ) Server (ClientStreaming): Request( time: 1733912946697, num: 2 ) Client (ClientStreaming) finished, received: Response( time: 1733912946696922 , num: 3 )
サーバは RPC を失敗させる初期メッセージを受 信し、再試行ロジックをトリガする。再試行は有効なメッセー ジで RPC を開始し、その後、3 つのメッセー ジがサーバに送信された後、優雅に完了し ます。
双方向ストリーミング
双方向ストリーミングは、クライアントとサーバーの両方が同時にメッセージを送受信することができ、最も柔軟性があります。これは、finished とmessageReceived シグナルを処理する必要があり、writeMessage を通じて書き込み機能を提供する。
ハンドラをクラスのメンバとして組み込み、メンバ関数スロット接続によるクラスベースのアプローチで機能を実証する。さらに、ポインタ・ベースのread 関数を利用する。使用するメンバは以下の2つである:
std::unique_ptr<QGrpcBidiStream> m_bidiStream; guide::Response m_bidiResponse;
初期メッセージから双方向ストリーミングを開始する関数を作成し、スロット関数をfinished とmessageReceived の各シグナルに接続する。
void bidirectionalStreaming(const guide::Request &initialRequest) { m_bidiStream = m_client.BidirectionalStreaming(initialRequest); connect(m_bidiStream.get(), &QGrpcBidiStream::finished, this, &ClientGuide::bidiFinished); connect(m_bidiStream.get(), &QGrpcBidiStream::messageReceived, this, &ClientGuide::bidiMessageReceived); }
スロットの機能は単純です。finished スロットは単純に RPC オブジェクトを表示し、リセットします:
voidbidiFinished(constQGrpcStatus&status) {if(status.isOk()) qDebug("Client (BidirectionalStreaming) finished"); その他 qDebug() << "Client (BidirectionalStreaming) failed:" << status; m_bidiStream.reset(); }
messageReceived スロットreads をm_bidiResponse
メンバーに入れ、受信した応答番号がゼロになるまでメッセージを書き続ける。その時点で、writesDone を使ってクライアント側の通信を半分閉じます。
voidbidiMessageReceived() {if(m_bidiStream->read(&m_bidiResponse)) { (m_bidiStream->read(&m_bidiResponse)){」。 qDebug() << "Client (BidirectionalStreaming) received:" << m_bidiResponse; if(m_bidiResponse.num()> 0) { m_bidiStream->writeMessage(createRequest(m_bidiResponse.num()- 1));return; } }else{. qDebug("Client (BidirectionalStreaming) deserialization failed"); } m_bidiStream->writesDone(); }
コードの実行
サーバーロジックは、何かを読み取ったらすぐにメッセージを返し、リクエストの番号でレスポンスを作成します。main
では、このようなリクエストを作成し、最終的にカウンタとして機能します。
clientGuide.bidirectionalStreaming(ClientGuide::createRequest(3));
双方向ストリーミングを実行した場合の出力は次のようになる:
Welcome to the clientguide! Starting the server process ... Server listening on: localhost:50056 Server (BidirectionalStreaming): Request( time: 1733503832107, num: 3 ) Client (BidirectionalStreaming) received: Response( time: 1733503832108708 , num: 3 ) Server (BidirectionalStreaming): Request( time: 1733503832109, num: 2 ) Client (BidirectionalStreaming) received: Response( time: 1733503832109024 , num: 2 ) Server (BidirectionalStreaming): Request( time: 1733503832109, num: 1 ) Client (BidirectionalStreaming) received: Response( time: 1733503832109305 , num: 1 ) Server (BidirectionalStreaming): Request( time: 1733503832109, num: 0 ) Client (BidirectionalStreaming) received: Response( time: 1733503832109529 , num: 0 ) Client (BidirectionalStreaming) finished
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.