「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > RPC アクション EPUProtobuf の使用とカスタム プラグインの作成

RPC アクション EPUProtobuf の使用とカスタム プラグインの作成

2024 年 9 月 12 日に公開
ブラウズ:203

RPC Action EPUsing Protobuf and Creating a Custom Plugin

前の記事では、net/rpc パッケージを使用して単純な RPC インターフェイスを実装し、net/rpc に付属する Gob エンコーディングと JSON エンコーディングを試して、Golang の基本を学びましたRPC。この投稿では、net/rpc と protobuf を組み合わせて、コードの生成に役立つ protobuf プラグインを作成します。それでは、始めましょう。

この記事は、中型 MPP プランで初めて公開されました。あなたが Medium ユーザーであれば、Medium で私をフォローしてください。どうもありがとうございます。

作業中に gRPC と protobuf を使用したはずですが、これらはバインドされていません。 gRPC は JSON を使用してエンコードでき、protobuf は他の言語で実装できます。

プロトコル バッファー (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

protoc用プラグインシステム

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 関連のコードを生成できます。

Protoc プラグインをカスタマイズする

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 が導入されました。

  1. まず、protoc-gen-go-spprpc などの Go 言語プロジェクトを作成します。
  2. 次に、protogen.Options を定義し、その Run メソッドを呼び出して、func(*protogen.Plugin) エラー コールバックを渡す必要があります。これでメイン処理コードは終了です。
  3. protogen.Options の ParamFunc パラメータを設定することもできます。これにより、protogen はコマンド ラインから渡されたパラメータを自動的に解析します。標準入力からの protobuf 情報の読み取りとデコード、入力情報の protobuf へのエンコード、stdout の書き込みなどの操作はすべて protogen によって処理されます。私たちがする必要があるのは、protogen.Plugin と対話してコード生成ロジックを実装することです。

各サービスで最も重要なことはサービスの名前であり、次に各サービスには一連のメソッドがあります。サービスによって定義されたメソッドの場合、最も重要なのはメソッドの名前、入力パラメータの名前、出力パラメータのタイプです。まず、サービスのメタ情報を記述する 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 を完全に理解するための道となります。皆さんもこの技術をマスターできることを願っています。

参照

  1. https://taoshu.in/go/create-protoc-plugin.html
  2. https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-02-pb-intro.html
リリースステートメント この記事は次の場所に転載されています: https://dev.to/huizhou92/rpc-action-ep2-using-protobuf-and-creating-a-custom-plugin-2j9j?1 侵害がある場合は、study_golang@163 までご連絡ください。 .comを削除してください
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3