ヒスねこTechBlog

日々の気になる技術をまとめてます。

gRPCをラズパイ3+Pythonで試す

gRPCを試してみました。

準備

gRPC試したいだけなら環境は正味なんでもよいと思いますが、今回はラズパイ3でPython製サーバを立ててみます。

まずはPythonの開発環境を整えます。Raspberry Pi OSは標準でPythonが入っていましたが、pipは入っていませんでした。gRPCでの開発に必要なパッケージがpipで入るので、導入しておきます。

$ sudo apt install python3-pip
$ python -m pip install --upgrade pip

次に、gRPCのパッケージをインストールします。

$ python -m pip install grpcio
$ python -m pip install grpcio-tools
protoの作成

これで準備は整ったので、protoファイルを作成してスタブを生成したのち、サーバ/クライアントのプログラムを作成すればgRPCによるやりとりができるそう。まずはprotoファイルから。sample.protoとして作成しました。

syntax = "proto3";

package sample;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

GreeterサービスにSayHelloメソッドを持たせます。このメソッドは、string型のnameを持つHelloRequestメッセージを受け取り、同じくstring型のmessageを持つHelloReplyメッセージを返します。

nameやmessageといったフィールドに「=1」とあるのは、代入ではなくタグナンバーの割当てだそうで、数字はメッセージ内で重複してはいけないとのこと。長くプログラムを書いているとnameやmessageに1が入るように錯覚しますね。私は初見でちょっと混乱しました。

サーバ/クライアントプログラムで使用するスタブを生成します。今回作成した程度の規模であれば、生成されたPythonファイルもあまり大きくないので、試しに中身を確認してみてもいいかもしれません。

$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./sample.proto
$ ls
# 左の2つが生成された
sample_pb2_grpc.py  sample_pb2.py  sample.proto
サーバ側の作成

gRPCにconcurrent.futuresモジュールのThreadPoolExecutorを渡してあげる必要があるので、importを忘れないようにしてください。ファイル名はserver.pyとしました。

from concurrent import futures
import grpc
import sample_pb2
import sample_pb2_grpc

class Sample(sample_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        message = "Hello %s." % request.name
        return sample_pb2.HelloReply(message=message)

def run_server():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=5))
    sample_pb2_grpc.add_GreeterServicer_to_server(Sample(), server)
    server.add_insecure_port('[::]:8080')
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    run_server()

max_workersやポート番号などはお好みで設定してください。なおadd_insecure_port()は平文での接続になるため、SSL/TLSによる暗号化をしたい場合はadd_secure_port()を使うとのこと。こちらは後日改めて試そうと思います。

クライアント側の作成

次にクライアント側を作りますが、ポート番号はサーバ側と一致させ、リクエストの内容(name)はお好みで。ファイル名はclient.pyとしました。

import grpc
import sample_pb2
import sample_pb2_grpc

def run_client():
    with grpc.insecure_channel('localhost:8080') as ch:
        stub = sample_pb2_grpc.GreeterStub(ch)
        reply = stub.SayHello(sample_pb2.HelloRequest(name='Bob'))
    print("Reply: %s" % reply.message)

if __name__ == '__main__':
    run_client()
動作確認

以下でサーバとクライアントをそれぞれ実行します。クライアントは別端末で実行することを推奨します。確認できたらサーバ側はCtrl+Cで停止してください。

# 端末1
$ python ./server.py
# 端末2
$ python ./client.py
Reply: Hello Bob.