前の記事では、net/rpc パッケージを使用して単純な RPC インターフェイスを実装し、net/rpc に付属する Gob エンコーディングと JSON エンコーディングを試して、Golang の基本を学びましたRPC。この投稿では、net/rpc と protobuf を組み合わせて、コードの生成に役立つ protobuf プラグインを作成します。それでは、始めましょう。
この記事は、中型 MPP プランで初めて公開されました。あなたが Medium ユーザーであれば、Medium で私をフォローしてください。どうもありがとうございます。
作業中に gRPC と protobuf を使用したはずですが、これらはバインドされていません。 gRPC は JSON を使用してエンコードでき、protobuf は他の言語で実装できます。
プロトコル バッファー (Protobuf) は、構造化データのシリアル化に使用される無料のオープンソースのクロスプラットフォーム データ形式です。ネットワーク経由で相互に通信するプログラムの開発やデータの保存に役立ちます。この方法には、一部のデータの構造を記述するインターフェース記述言語と、その記述から構造化データを表すバイト ストリームを生成または解析するためのソース コードを生成するプログラムが含まれます。
まず、メッセージ「String」を定義するプロトファイル hello-service.proto を作成します
syntax = "proto3"; package api; option go_package="api"; message String { string value = 1; }
次に、protoc ユーティリティを使用してメッセージ String の Go コードを生成します
protoc --go_out=. hello-service.proto
次に、protobuf ファイルによって生成された文字列を使用するように Hello 関数の引数を変更します。
type HelloServiceInterface = interface { Hello(request api.String, reply *api.String) error }
使い方は以前と変わりませんが、文字列を直接使用するほど便利ではありません。では、なぜ protobuf を使用する必要があるのでしょうか?前に述べたように、Protobuf を使用して言語に依存しない RPC サービス インターフェイスとメッセージを定義し、次に protoc ツールを使用してさまざまな言語でコードを生成することに、その真の価値があります。たとえば、公式プラグイン protoc-gen-go を使用して gRPC コードを生成します。
protoc --go_out=plugins=grpc. hello-service.proto
protobuf ファイルからコードを生成するには、 protoc をインストールする必要がありますが、protoc はターゲット言語が何であるかを認識しないため、コードの生成を支援するプラグインが必要です。 protoc のプラグイン システムはどのように機能しますか?上記の grpc を例に挙げます。
ここには --go_out パラメータがあります。呼び出しているプラグインは protoc-gen-go なので、パラメーターは go_out と呼ばれます。名前が XXX の場合、パラメータの名前は XXX_out.
になります。
protoc が実行されると、最初に protobuf ファイルが解析され、プロトコル バッファーでエンコードされた記述データのセットが生成されます。まず go プラグインが protoc に含まれているかどうかを判断し、次に $PATH で protoc-gen-go を探します。見つからない場合はエラーを報告します。 protoc-gen-go を実行します。 protoc-gen-go コマンドを実行し、stdin 経由で説明データをプラグイン コマンドに送信します。プラグインはファイルの内容を生成した後、プロトコル バッファーでエンコードされたデータを標準出力に入力して、protoc に特定のファイルを生成するように指示します。
plugins=grpc は、protoc-gen-go を呼び出すために付属するプラグインです。これを使用しない場合、Go でメッセージが生成されるだけですが、このプラグインを使用して grpc 関連のコードを生成できます。
Hello インターフェイスのタイミングを protobuf に追加した場合、コードを直接生成するように protoc プラグインをカスタマイズできますか?
syntax = "proto3"; package api; option go_package="./api"; service HelloService { rpc Hello (String) returns (String) {} } message String { string value = 1; }
この記事での私の目標は、次のような RPC サーバー側コードとクライアント側コードを生成するために使用されるプラグインを作成することでした。
// HelloService_rpc.pb.go type HelloServiceInterface interface { Hello(String, *String) error } func RegisterHelloService( srv *rpc.Server, x HelloServiceInterface, ) error { if err := srv.RegisterName("HelloService", x); err != nil { return err } return nil } type HelloServiceClient struct { *rpc.Client } var _ HelloServiceInterface = (*HelloServiceClient)(nil) func DialHelloService(network, address string) ( *HelloServiceClient, error, ) { c, err := rpc.Dial(network, address) if err != nil { return nil, err } return &HelloServiceClient{Client: c}, nil } func (p *HelloServiceClient) Hello( in String, out *String, ) error { return p.Client.Call("HelloService.Hello", in, out) }
これにより、ビジネス コードは次のように変更されます
// service func main() { listener, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal("ListenTCP error:", err) } _ = api.RegisterHelloService(rpc.DefaultServer, new(HelloService)) for { conn, err := listener.Accept() if err != nil { log.Fatal("Accept error:", err) } go rpc.ServeConn(conn) } } type HelloService struct{} func (p *HelloService) Hello(request api.String, reply *api.String) error { log.Println("HelloService.proto Hello") *reply = api.String{Value: "Hello:" request.Value} return nil } // client.go func main() { client, err := api.DialHelloService("tcp", "localhost:1234") if err != nil { log.Fatal("net.Dial:", err) } reply := &api.String{} err = client.Hello(api.String{Value: "Hello"}, reply) if err != nil { log.Fatal(err) } log.Println(reply) }
生成されたコードに基づくと、ワークロードはすでにはるかに小さくなり、エラーの可能性もすでに非常に低くなります。良いスタートです。
上記の API コードに基づいて、テンプレート ファイルを抽出できます:
const tmplService = ` import ( "net/rpc") type {{.ServiceName}}Interface interface { func Register{{.ServiceName}}( if err := srv.RegisterName("{{.ServiceName}}", x); err != nil { return err } return nil} *rpc.Client} func Dial{{.ServiceName}}(network, address string) ( {{range $_, $m := .MethodList}} return p.Client.Call("{{$root.ServiceName}}.{{$m.MethodName}}", in, out)} `
テンプレート全体は明確で、その中には MethodName、ServiceName などのプレースホルダーがいくつかありますが、これについては後で説明します。
Google は Go 言語 API 1 をリリースしました。これにより、プラグイン開発の難易度が大幅に軽減される新しいパッケージ google.golang.org/protobuf/compile R/protogen が導入されました。
各サービスで最も重要なことはサービスの名前であり、次に各サービスには一連のメソッドがあります。サービスによって定義されたメソッドの場合、最も重要なのはメソッドの名前、入力パラメータの名前、出力パラメータのタイプです。まず、サービスのメタ情報を記述する ServiceData を定義しましょう:
// ServiceData type ServiceData struct { PackageName string ServiceName string MethodList []Method } // Method type Method struct { MethodName string InputTypeName string OutputTypeName string }
次に、メイン ロジック、コード生成ロジック、そして最後にコードを生成するための tmpl の呼び出しが続きます。
func main() { protogen.Options{}.Run(func(gen *protogen.Plugin) error { for _, file := range gen.Files { if !file.Generate { continue } generateFile(gen, file) } return nil }) } // generateFile function definition func generateFile(gen *protogen.Plugin, file *protogen.File) { filename := file.GeneratedFilenamePrefix "_rpc.pb.go" g := gen.NewGeneratedFile(filename, file.GoImportPath) tmpl, err := template.New("service").Parse(tmplService) if err != nil { log.Fatalf("Error parsing template: %v", err) } packageName := string(file.GoPackageName) // Iterate over each service to generate code for _, service := range file.Services { serviceData := ServiceData{ ServiceName: service.GoName, PackageName: packageName, } for _, method := range service.Methods { inputType := method.Input.GoIdent.GoName outputType := method.Output.GoIdent.GoName serviceData.MethodList = append(serviceData.MethodList, Method{ MethodName: method.GoName, InputTypeName: inputType, OutputTypeName: outputType, }) } // Perform template rendering err = tmpl.Execute(g, serviceData) if err != nil { log.Fatalf("Error executing template: %v", err) } } }
最後に、コンパイルされたバイナリ実行ファイル protoc-gen-go-spprpc を $PATH に配置し、protoc を実行して必要なコードを生成します。
protoc --go_out=.. --go-spprpc_out=.. HelloService.proto
protoc-gen-go-spprpc は protoc に依存して実行する必要があるため、デバッグが少し難しくなります。
を使用できます
fmt.Fprintf(os.Stderr, "Fprintln: %v\n", err)
デバッグのためにエラー ログを印刷します。
この記事は以上です。まず protobuf を使用して RPC 呼び出しを実装し、次にコードの生成に役立つ protobuf プラグインを作成しました。これは、protobuf RPC を学ぶための扉を開き、gRPC を完全に理解するための道となります。皆さんもこの技術をマスターできることを願っています。
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3