gRPCサーバをサーバサイドストリーミング対応に拡張する
前回では、Unary方式の手続きを実装するgRPCサーバアプリケーションを作成しました。これに、サーバサイドストリーミング方式の手続きを追加します。SayHello手続きの拡張版として、サーバサイドストリーミングにてメッセージを複数返してくるPlentySayHello手続きを実装してみます。
プロトコル定義ファイルへの手続きの追加
プロトコル定義ファイルProto/greet.protoにおいて、GreeterサービスにPlentySayHello手続きを追加し、リクエストのためのメッセージPlentyHelloRequestを追加します。個々のレスポンスの形式は変わらないので、HelloReplyメッセージはSayHello手続きと共通とします。
…略… service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); // 複数回対応のSayHello手続き rpc PlentySayHello (PlentyHelloRequest) returns (stream HelloReply); (1) } …略… // 複数回対応のリクエストメッセージ message PlentyHelloRequest { (2) string name = 1; int32 count = 2; }
(1)が、追加したPlentySayHello手続きです。SayHello手続きとほとんど同じように見えますが、戻り値にstreamキーワードが付いている点に注目してください。これは、手続きがサーバサイドストリーミング方式に対応することを示すキーワードです。なお、クライアントサイドストリーミングでは引数の方にstreamを指定します。
(2)は、PlentySayHello手続きの引数であるPlentyHelloRequestメッセージの定義です。HelloRequestメッセージと異なるのは、int32型のcountフィールドを追加してレスポンスの回数を指定している点です。サーバサイドストリーミングにより、指定した回数だけサーバからレスポンスHelloReplyが返るようになります。
サービスへの手続きの追加
ここでdotnet buildコマンドを実行すると、修正されたプロトコル定義ファイルに基づきスタブファイルが再作成されます。この時点で、Services/GreeterService.csファイルにPlentySayHelloメソッドを実装できます。スタブに追加されたPlentySayHelloメソッドの定義に基づき、Services/GreeterService.csファイルにメソッドを実装してみます。
…略… public override async Task PlentySayHello(PlentyHelloRequest request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context) (1) { for (var count = 1; count <= request.Count; count++) { var reply = new HelloReply { Message = $"({count})Hello " + request.Name }; await responseStream.WriteAsync(reply); (2) } return; } …略…
(1)の通り、サーバサイドストリーミングに対応したPlentySayHelloメソッドのシグネチャは、Unaryに対応するSayHelloメソッドと異なっています。SayHelloメソッドがHelloRequest型とServerCallContext型を引数に受け取ってTask<HelloReply>型を返すのに対して、PlentySayHelloメソッドは第2引数にIServerStreamWriter<HelloReply>型が追加となっており、戻り値は単なるTask型となります。
IServerStreamWriterは、その名の通りストリームの書き込みのためのインタフェースで、メソッドの戻り値ではなくこのインスタンスを使って非同期にレスポンスを返します。このため、メソッドの戻り値は特に必要ないのです。(2)がその処理であり、生成したHelloReplyオブジェクトをWriteAsyncメソッドによりストリームに書き込んでいます。
[NOTE]Task型
Task型は、非同期メソッドの戻り値の型であり、これを戻り値とするメソッドはawait演算子によって実行の終了を待機することができます(単にvoid型を戻す場合は、待機できません)。非同期メソッドが値を返したい場合には、戻り値をTask<TResult>(TResultは値の型)とします。Unary方式の手続きではメソッドが値を返したいのでTask<HelloReply>型を戻り値としていたのに対し、サーバサイドストリーミングの手続きではメソッドは特に値を返す必要はないので、単なるTaskを戻り値としています。後述の、サーバサイドストリーミング対応のクライアントのコードは、これを踏まえて読んでください。
ここでサーバを起動し、grpcurlで動作を確認して、以下のように出力されれば実装は成功です。
% grpcurl -plaintext -proto Protos/greet.proto -d '{"name": "Nao", "count": 5}' localhost:5046 greet.Greeter/PlentySayHello { "message": "(1)Hello Nao" } …略(番号を変えて計5回表示される)…
サーバサイドストリーミング対応に合わせたクライアントの拡張
gRPCサーバをサーバサイドストリーミング対応に拡張したので、それに合わせてgRPCクライアントも拡張してみます。
プロトコル定義ファイルの修正
上記で拡張したサーバアプリケーションのプロトコル定義ファイルProtos/greet.protoの修正を、クライアントアプリケーションのProtos/greet.protoファイルにも反映します。改めてコピーして、名前空間だけ修正しても構いません。
クライアントコードの追加
サーバサイドストリーミングに対応したクライアントのコードを追加します。このクライアントは、第1引数にあいさつ相手の名前、第2引数が指定されていればあいさつの回数として受け取り、回数が1回ならUnary、2回以上ならサーバサイドストリーミングで手続きを呼び出して結果を表示します。
using System.Threading.Tasks; (1) using Grpc.Core; …略… // 引数の取得 if (args.Length < 1) (2) { Console.WriteLine("引数が1個以上必要です"); return; } var name = args[0]; var count = 1; if (args.Length > 1) { Int32.TryParse(args[1], out count); if (count < 1) { Console.WriteLine("回数は1以上にしてください"); return; } } // チャネルとクライアントの生成 using var channel = GrpcChannel.ForAddress($"http://localhost:5046"); (3) var client = new Greeter.GreeterClient(channel); if (count == 1) { // Unary。SayHelloAsyncメソッドを非同期で呼び出し結果を表示 …Unaryと同じなので略… } else { // サーバサイドストリーミング対応。PlentySayHelloメソッドを呼び出し、 // 結果を非同期で受け取って順番に表示 using var response = client.PlentySayHello( new PlentyHelloRequest { Name = name, Count = count}); (4) await foreach (var reply in response.ResponseStream.ReadAllAsync()) (5) { Console.WriteLine(reply.Message); } }
(1)は、必要な名前空間のインポートです。特に、Grpc.Coreはサーバサイドストリーミングで必要になってくるので、スタブコードで指定されているものを漏らさず指定する必要があります。
(2)は、引数の処理です。あいさつ相手の名前はnameに、回数はcountにセットします。条件を満たさない場合にはメッセージを表示してプログラムを終了します。
(3)は、既述の通りチャネルの生成とクライアントの生成です。
(4)は、サーバサイドストリーミングの場合の手続き呼び出しです。PlentySayHelloメソッドの戻り値は、あくまでも手続きの戻り値(Task型)を受け取るためのものであることに注目してください(上記の[NOTE]参照)。続く非同期foreach文にて、ReadAsyncAllメソッドを呼び出して順番にレスポンスを受け取り、表示しています。
この時点で、dotnet runコマンドを実行し、以下のように実行結果を確認できればクライアントの作成は成功です。
% dotnet run Nao 5 (1)Hello Nao …略(番号を変えて計5回表示される)…
まとめ
前回と今回は、ASP.NET CoreにおけるgRPCのサポートについて見てきました。次回は、リアルタイム通信のフレームワークであるSignalRを紹介します。