」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > RPC 操作 EPU 使用 Protobuf 並建立自訂插件

RPC 操作 EPU 使用 Protobuf 並建立自訂插件

發佈於2024-11-08
瀏覽:253

RPC Action EPUsing Protobuf and Creating a Custom Plugin

上一篇文章中,我使用net/rpc包实现了一个简单的RPC接口,并尝试了net/rpc自带的Gob编码和JSON编码,学习了Golang的一些基础知识远程过程调用。在这篇文章中,我将结合 net/rpc 和 protobuf 并创建我的 protobuf 插件来帮助我们生成代码,所以让我们开始吧。

本文首发于Medium MPP计划。如果您是 Medium 用户,请在 Medium 上关注我。非常感谢。

我们在工作中肯定使用过gRPC和protobuf,但是它们并没有绑定。 gRPC可以使用JSON编码,protobuf可以使用其他语言实现。

Protocol BuffersProtobuf)是一种免费开源跨平台数据格式,用于序列化结构化数据。它对于开发通过网络相互通信或存储数据的程序很有用。该方法涉及一种描述某些数据结构的接口描述语言和一个根据该描述生成源代码的程序,用于生成或解析表示结构化数据的字节流。

使用 protobuf 的示例

首先我们编写一个proto文件hello-service.proto,它定义了一条消息“String”

syntax = "proto3";
package api;
option  go_package="api";

message String {
  string value = 1;
}

然后使用protoc实用程序生成消息String的Go代码

protoc --go_out=. hello-service.proto

然后我们修改Hello函数的参数以使用protobuf文件生成的字符串。

type HelloServiceInterface = interface {  
    Hello(request api.String, reply *api.String) error  
}  

使用起来和以前没有什么不同,甚至不如直接使用string方便。那么我们为什么要使用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文件,生成一组Protocol Buffers编码的描述性数据。它首先会判断protoc中是否包含go插件,然后会尝试在$PATH中寻找protoc-gen-go,如果找不到就会报错,然后将运行 protoc-gen-go。 protoc-gen-go 命令并通过 stdin 将描述数据发送到插件命令。插件生成文件内容后,会将Protocol Buffers编码的数据输入到stdout,告诉protoc生成特定的文件。

plugins=grpc 是 protoc-gen-go 附带的一个插件,以便调用它。如果你不使用它,它只会在Go中生成一条消息,但是你可以使用这个插件来生成grpc相关的代码。

自定义协议插件

如果我们在protobuf中添加Hello接口时序,是否可以自定义一个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. 首先我们创建一个go语言项目,如protoc-gen-go-spprpc
  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如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 在 Go 中使用 WebSocket 進行即時通信
    在 Go 中使用 WebSocket 進行即時通信
    构建需要实时更新的应用程序(例如聊天应用程序、实时通知或协作工具)需要比传统 HTTP 更快、更具交互性的通信方法。这就是 WebSockets 发挥作用的地方!今天,我们将探讨如何在 Go 中使用 WebSocket,以便您可以向应用程序添加实时功能。 在这篇文章中,我们将介绍: WebSocke...
    程式設計 發佈於2024-12-26
  • 如何在 HTML 表格中有效地使用 Calc() 和基於百分比的欄位?
    如何在 HTML 表格中有效地使用 Calc() 和基於百分比的欄位?
    在表格中使用Calc():克服百分比困境創建具有固定寬度列和可變寬度列的表格可能具有挑戰性,尤其是在嘗試在其中使用calc() 函數。 在 HTML 中,使用 px 或 em 設定固定列寬非常簡單。但是,對於可變寬度列,通常使用百分比 (%) 單位。然而,當在表中使用 calc() 時,百分比似乎無...
    程式設計 發佈於2024-12-26
  • Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta:列偏移的刪除和恢復Bootstrap 4 在其Beta 1 版本中引入了重大更改柱子偏移了。然而,隨著 Beta 2 的後續發布,這些變化已經逆轉。 從 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    程式設計 發佈於2024-12-26
  • 如何在PHP中透過POST提交和處理多維數組?
    如何在PHP中透過POST提交和處理多維數組?
    在PHP 中透過POST 提交多維數組當使用具有可變長度的多列和行的PHP 表單時,有必要進行轉換輸入到多維數組中。這是解決這項挑戰的方法。 首先,為每列分配唯一的名稱,例如:<input name="topdiameter[' current ']" type="...
    程式設計 發佈於2024-12-26
  • for(;;) 迴圈到底是什麼、它是如何運作的?
    for(;;) 迴圈到底是什麼、它是如何運作的?
    揭秘神秘的for(;;) 循環在古老的程式碼庫深處,你偶然發現了一個令人困惑的奇特for 循環你的理解。其顯示如下:for (;;) { //Some stuff }您深入研究線上資源,但發現自己陷入沉默。讓我們來剖析這個神秘的構造。 for 迴圈的結構Java 中的for 迴圈遵循特定的語...
    程式設計 發佈於2024-12-25
  • Java 的 Scanner.useDelimiter() 如何使用正規表示式?
    Java 的 Scanner.useDelimiter() 如何使用正規表示式?
    Java 使用Scanner.useDelimiter 了解分隔符號Java 中使用Scanner.useDelimiter 了解分隔符號Java 中的Scanner 類別提供了useDelimiter 方法,讓您指定分隔符號(代字或模式)來分隔代字幣。然而,使用分隔符號可能會讓初學者感到困惑。讓我...
    程式設計 發佈於2024-12-25
  • 如何在 Android 中顯示動畫 GIF?
    如何在 Android 中顯示動畫 GIF?
    在Android 中顯示動畫GIF儘管最初誤解Android 不支援動畫GIF,但實際上它具有解碼和顯示動畫的能力顯示它們。這是透過利用 android.graphics.Movie 類別來實現的,儘管這方面沒有廣泛記錄。 要分解動畫 GIF 並將每個幀作為可繪製對象合併到 AnimationDra...
    程式設計 發佈於2024-12-25
  • 為什麼我在執行 phpize 時出現「找不到 config.m4」錯誤?
    為什麼我在執行 phpize 時出現「找不到 config.m4」錯誤?
    解決phpize 中的“找不到config.m4”錯誤在運行phpize 時遇到“找不到config.m4”錯誤是可能阻礙ffmpeg 等擴充安裝的常見問題。以下是解決此錯誤並讓 phpize 啟動並運行的方法。 先決條件:您已經安裝了適合您的PHP 版本的必要開發包,例如php- Debian/U...
    程式設計 發佈於2024-12-25
  • 列印時如何在每頁重複表頭?
    列印時如何在每頁重複表頭?
    在印刷模式下重複表格標題當表格在印刷過程中跨越多個頁面時,通常需要有標題行(TH元素)在每頁重複,以便於參考。 CSS 提供了一種機制來實現此目的。 解決方案:使用 THEAD 元素CSS 中的 THEAD 元素是專門為此目的而設計的。它允許您定義一組應在每個列印頁面上重複的標題行。使用方法如下:將...
    程式設計 發佈於2024-12-25
  • 儘管程式碼有效,為什麼 POST 請求無法擷取 PHP 中的輸入?
    儘管程式碼有效,為什麼 POST 請求無法擷取 PHP 中的輸入?
    解決PHP 中的POST 請求故障在提供的程式碼片段中:action=''而非:action="<?php echo $_SERVER['PHP_SELF'];?>";?>"檢查$_POST陣列:表單提交後使用 var_dump 檢查 $_POST 陣列的內...
    程式設計 發佈於2024-12-25
  • 如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    在 PHP 中組合關聯數組在 PHP 中,將兩個關聯數組組合成一個數組是常見任務。考慮以下請求:問題描述:提供的代碼定義了兩個關聯數組,$array1 和 $array2。目標是建立一個新陣列 $array3,它合併兩個陣列中的所有鍵值對。 此外,提供的陣列具有唯一的 ID,而名稱可能重疊。要求是建...
    程式設計 發佈於2024-12-25
  • 為什麼 `cout` 會誤解 `uint8_t` 以及如何修復它?
    為什麼 `cout` 會誤解 `uint8_t` 以及如何修復它?
    深入分析:為什麼 uint8_t 無法正確列印您遇到了 uint8_t 變數的值無法正確列印的問題庫特。經過調查,您發現將資料類型變更為 uint16_t 可以解決該問題。此行為源自於 uint8_t 的基本性質以及 cout 處理字元資料的方式。 uint8_t 在內部儲存一個無符號 8 位元整數...
    程式設計 發佈於2024-12-25
  • MySQLnd 是否已啟用?
    MySQLnd 是否已啟用?
    確定 MySQLnd 驅動程式狀態在 PHP 中使用 MySQL 時,驗證 MySQLnd 是否是活動資料庫驅動程式至關重要。與舊版驅動程式相比,此高級驅動程式提供了增強的效能和功能。 使用MySQLi 檢查MySQLnd對於MySQLi 連接,您可以使用以下程式碼來確定MySQLnd狀態:$mys...
    程式設計 發佈於2024-12-25
  • HTML 格式標籤
    HTML 格式標籤
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    程式設計 發佈於2024-12-25
  • 如何從類別名稱字串動態實例化 C++ 物件?
    如何從類別名稱字串動態實例化 C++ 物件?
    從類別名稱字串進行靈活的物件實例化簡介在物件導向程式設計中,實例化是常見的做法來自特定類別的物件。然而,在某些情況下,基於儲存為字串的類別名稱動態建立物件可能是有利的。這可以提供更高的靈活性和程式碼可維護性。本文探討了在 C 中實現動態物件建立的可能性。 使用字串到類型轉換的動態物件實例化不幸的是,...
    程式設計 發佈於2024-12-25

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3