”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > RPC 操作 EPU 使用 Protobuf 并创建自定义插件

RPC 操作 EPU 使用 Protobuf 并创建自定义插件

发布于2024-09-12
浏览:871

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]删除
最新教程 更多>
  • VLONE Clothing:重新定义都市时尚的街头服饰品牌
    VLONE Clothing:重新定义都市时尚的街头服饰品牌
    VLONE 是少数几个在快速变化的市场中取得超越街头服饰行业所能想象的成就的品牌之一。 VLONE 由 A$AP Mob 集体的电影制片人之一 A$AP Bari 创立,现已发展成为一个小众项目,有时甚至成为都市时尚界的国际知名品牌。 VLONE 凭借大胆的图案、深厚的文化联系和限量版发售,在时尚界...
    编程 发布于2024-11-07
  • 如何使用PDO查询单行中的单列?
    如何使用PDO查询单行中的单列?
    使用 PDO 查询单行中的单列处理针对单行中特定列的 SQL 查询时,通常需要检索直接取值,无需循环。要使用 PDO 完成此操作,fetchColumn() 方法就派上用场了。fetchColumn() 的语法为:$col_value = $stmt->fetchColumn([column_...
    编程 发布于2024-11-07
  • 我是如何构建 PeerSplit:一款免费的点对点费用分摊应用程序 — 从构思到发布仅需数周时间
    我是如何构建 PeerSplit:一款免费的点对点费用分摊应用程序 — 从构思到发布仅需数周时间
    我构建了 PeerSplit——一个免费的、点对点的 Splitwise 替代品——从想法到发布仅用了两周时间! PeerSplit 是一款本地优先的应用程序,用于分配团体费用。它可以离线工作,100% 免费且私密,不需要注册或任何个人数据。 以下是我如何构建它以及我在此过程中学到的一切。 ...
    编程 发布于2024-11-07
  • 如何在 PHP 中解析子域名的根域名?
    如何在 PHP 中解析子域名的根域名?
    在 PHP 中从子域解析域名在 PHP 中,从子域中提取根域名是一项常见任务。当您需要识别与子域关联的主网站时,这非常有用。为了实现这一目标,让我们探索一个解决方案。提供的代码片段利用 parse_url 函数将 URL 分解为其组件,包括域名。随后,它使用正则表达式来隔离根域,而忽略子域。以下示例...
    编程 发布于2024-11-07
  • 使用 Socket.io 构建实时应用程序
    使用 Socket.io 构建实时应用程序
    介绍 Socket.io 是一个 JavaScript 库,允许 Web 客户端和服务器之间进行实时通信。它支持创建交互式动态应用程序,例如聊天室、多人游戏和直播。凭借其易于使用的 API 和跨平台兼容性,Socket.io 已成为构建实时应用程序的流行选择。在本文中,我们将探讨 ...
    编程 发布于2024-11-07
  • 重写 `hashCode()` 和 `equals()` 如何影响 HashMap 性能?
    重写 `hashCode()` 和 `equals()` 如何影响 HashMap 性能?
    了解 equals 和 hashCode 在 HashMap 中的工作原理Java 中的 HashMap 使用 hashCode() 和 equals() 方法的组合来有效地存储和检索键值对。当添加新的键值对时,首先计算键的hashCode()方法,以确定该条目将被放置在哪个哈希桶中。然后使用 eq...
    编程 发布于2024-11-07
  • 使用 Google Apps 脚本和 Leaflet.js 构建交互式 XY 图像图
    使用 Google Apps 脚本和 Leaflet.js 构建交互式 XY 图像图
    Google Maps has a ton of features for plotting points on a map, but what if you want to plot points on an image? These XY Image Plot maps are commonly...
    编程 发布于2024-11-07
  • 理解 React 中的状态变量:原因和方法
    理解 React 中的状态变量:原因和方法
    在深入研究状态变量之前,让我们先来分析一下 React 组件的工作原理! 什么是 React 组件? 在 React 中,组件是一段可重用的代码,代表用户界面 (UI) 的一部分。它可以像 HTML 按钮一样简单,也可以像完整的页面一样复杂。 React 中的状态...
    编程 发布于2024-11-07
  • Miva 的日子:第 4 天
    Miva 的日子:第 4 天
    这是 100 天 Miva 编码挑战的第四天。我跳过了第三天的报告,因为我被困在我的网页设计项目中,需要改变节奏。这就是为什么我今天决定深入研究 JavaScript。 JavaScript JavaScript 就像系统和网站的行为组件。它为网站增加了交互性和响应能力,使其成为网页设计和开发的关...
    编程 发布于2024-11-07
  • TailGrids React:+ Tailwind CSS React UI 组件
    TailGrids React:+ Tailwind CSS React UI 组件
    我们很高兴推出 TailGrids React,这是您的新首选工具包,可用于轻松构建令人惊叹的响应式 Web 界面。 TailGrids React 提供了超过 600 免费和高级 React UI 组件、块、部分和模板的大量集合 - 所有这些都是用 Tailwind CSS 精心制作的。 无论您...
    编程 发布于2024-11-07
  • 如何用列表值反转字典?
    如何用列表值反转字典?
    使用列表值反转字典:解决方案在本文中,我们探讨了使用列表值反转字典的挑战。给定一个索引字典,其中键是文件名,值是这些文件中出现的单词列表,我们的目标是创建一个倒排字典,其中单词是键,值是文件名列表。提供的反转函数 invert_dict,不适用于以列表值作为键的字典,因为它会失败并显示“TypeEr...
    编程 发布于2024-11-07
  • 现代 Web 开发框架:比较流行的框架及其用例
    现代 Web 开发框架:比较流行的框架及其用例
    在快速发展的 Web 开发领域,选择正确的框架可以显着影响项目的成功。本文深入研究了一些最流行的 Web 开发框架,比较了它们的优势和理想用例,以帮助开发人员做出明智的决策。 反应 概述 React 由 Facebook 开发和维护,是一个用于构建用户界面的 Java...
    编程 发布于2024-11-07
  • 如何在 Go 1.18 中安全地使用泛型类型解组 JSON?
    如何在 Go 1.18 中安全地使用泛型类型解组 JSON?
    Unmarshal 中的泛型使用 (Go 1.18)在 Go 1.18 中使用泛型时,例如创建一个容器来保存各种报告类型,可能会出现类型限制。考虑以下设置:由结构表示的多种报告类型具有通用类型参数的 ReportContainer 包装器可报告,约束为实现可报告接口鉴别器ReportType 在解组...
    编程 发布于2024-11-07
  • 了解 Effect-TS 中的选项排序
    了解 Effect-TS 中的选项排序
    示例 1:使用 O.andThen 忽略第一个值 概念 O.andThen 函数允许您执行两个选项的序列,其中结果仅由第二个选项决定。当第一个选项达到目的,但后续操作中不需要它的值时,这很有用。 代码 function sequencing_ex01...
    编程 发布于2024-11-07
  • React 初学者指南:基础知识入门
    React 初学者指南:基础知识入门
    React 已成为现代 Web 开发的基石,以其高效、灵活性和强大的生态系统而闻名。 React 由 Facebook 开发,允许开发人员创建可重用的 UI 组件,从而简化了构建交互式用户界面的过程。 无论您是想构建复杂的单页应用程序还是只是想提高您的 Web 开发技能,掌握 React 都是一笔...
    编程 发布于2024-11-07

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3