"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > EPU de acción RPC: uso de Protobuf y creación de un complemento personalizado

EPU de acción RPC: uso de Protobuf y creación de un complemento personalizado

Publicado el 2024-09-12
Navegar:912

RPC Action EPUsing Protobuf and Creating a Custom Plugin

En el artículo anterior, implementé una interfaz RPC simple usando el paquete net/rpc y probé la codificación Gob que viene con la codificación net/rpc y JSON para aprender algunos conceptos básicos de Golang. RPC. En esta publicación, combinaré net/rpc con protobuf y crearé mi complemento protobuf para ayudarnos a generar código, así que comencemos.

Este artículo se publicó por primera vez en el plan Medium MPP. Si eres usuario de Medium, sígueme en Medium. Muchas gracias.

Debemos haber usado gRPC y protobuf durante nuestro trabajo, pero no están vinculados. gRPC se puede codificar usando JSON y protobuf se puede implementar en otros idiomas.

Protocol Buffers (Protobuf) es un formato de datos multiplataforma gratuito y de código abierto que se utiliza para serializar datos estructurados. Es útil para desarrollar programas que se comunican entre sí a través de una red o para almacenar datos. El método implica un lenguaje de descripción de interfaz que describe la estructura de algunos datos y un programa que genera código fuente a partir de esa descripción para generar o analizar un flujo de bytes que representa los datos estructurados.

Un ejemplo de uso de protobuf

Primero escribimos un archivo proto hello-service.proto que define un mensaje "String"

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

message String {
  string value = 1;
}

Luego use la utilidad protoc para generar el código Go para la cadena del mensaje

protoc --go_out=. hello-service.proto

Luego modificamos los argumentos de la función Hello para usar el String generado por el archivo protobuf.

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

Usarlo no es diferente de antes, incluso, no es tan conveniente como usar una cadena directamente. Entonces, ¿por qué deberíamos utilizar protobuf? Como dije antes, usar Protobuf para definir mensajes e interfaces de servicio RPC independientes del idioma, y ​​luego usar la herramienta protoc para generar código en diferentes idiomas, es donde radica su valor real. Por ejemplo, utilice el complemento oficial protocolo-gen-go para generar código gRPC.

protoc --go_out=plugins=grpc. hello-service.proto

Sistema de complementos para protocolo

Para generar código a partir de archivos protobuf, debemos instalar el protocolo, pero el protocolo no sabe cuál es nuestro idioma de destino, por lo que necesitamos complementos que nos ayuden a generar código. ¿Cómo funciona el sistema de complementos de Protoc? Tome el grpc anterior como ejemplo.

Hay un parámetro --go_out aquí. Dado que el complemento que llamamos es protoc-gen-go, el parámetro se llama go_out; si el nombre fuera XXX, el parámetro se llamaría XXX_out.

Cuando protoc se está ejecutando, primero analizará el archivo protobuf y generará un conjunto de datos descriptivos codificados en Protocol Buffers. Primero determinará si el complemento go está incluido o no en el protocolo, y luego intentará buscar protoc-gen-go en $PATH, y si no puede encontrarlo, informará un error y luego ejecutará protocolo-gen-go. protoc-gen-go y envía los datos de descripción al comando del complemento a través de stdin. Después de que el complemento genera el contenido del archivo, ingresa datos codificados en Protocol Buffers en la salida estándar para indicarle al protocolo que genere el archivo específico.

plugins=grpc es un complemento que viene con protoc-gen-go para poder invocarlo. Si no lo usa, solo generará un mensaje en Go, pero puede usar este complemento para generar código relacionado con grpc.

Personalizar un complemento de protocolo

Si agregamos la sincronización de la interfaz Hello a protobuf, ¿podemos personalizar un complemento de protocolo para generar código directamente?

syntax = "proto3";  
package api;  
option  go_package="./api";  
service HelloService {  
  rpc Hello (String) returns (String) {}  
}  
message String {  
  string value = 1;
}

Objetivo

Para este artículo, mi objetivo era crear un complemento que luego se usaría para generar código RPC del lado del servidor y del lado del cliente que se vería así.

// 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)  
}

Esto cambiaría nuestro código comercial para que se parezca al siguiente

// 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)  
}

Según el código generado, nuestra carga de trabajo ya es mucho menor y las posibilidades de error ya son muy pequeñas. Un buen comienzo.

Según el código API anterior, podemos extraer un archivo de plantilla:

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)}  
`

Toda la plantilla es clara y contiene algunos marcadores de posición, como MethodName, ServiceName, etc., que cubriremos más adelante.

¿Cómo desarrollar un complemento?

Google lanzó la API 1 del lenguaje Go, que introduce un nuevo paquete google.golang.org/protobuf/compile R/protogen, que reduce en gran medida la dificultad del desarrollo de complementos:

  1. En primer lugar, creamos un proyecto de lenguaje go, como protoc-gen-go-spprpc
  2. Luego necesitamos definir un protogen.Options, luego llamar a su método Run y ​​pasar una devolución de llamada de error func(*protogen.Plugin). Este es el final del código del proceso principal.
  3. También podemos configurar el parámetro ParamFunc de protogen.Options, para que protogen analice automáticamente los parámetros pasados ​​por la línea de comando por nosotros. Protogen maneja operaciones como leer y decodificar información de protobuf desde la entrada estándar, codificar información de entrada en protobuf y escribir stdout. Lo que debemos hacer es interactuar con protogen.Plugin para implementar la lógica de generación de código.

Lo más importante para cada servicio es el nombre del servicio, y luego cada servicio tiene un conjunto de métodos. Para el método definido por el servicio, lo más importante es el nombre del método, así como el nombre del parámetro de entrada y el tipo de parámetro de salida. Primero definamos ServiceData para describir la metainformación del servicio:

// ServiceData 
type ServiceData struct {  
    PackageName string  
    ServiceName string  
    MethodList  []Method  
}
// Method 
type Method struct {  
    MethodName     string  
    InputTypeName  string  
    OutputTypeName string  
}

Luego viene la lógica principal, la lógica de generación de código y, finalmente, la llamada a tmpl para generar el código.

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)  
       }  
    }  
}

Complemento de depuración

Finalmente, colocamos el archivo de ejecución binario compilado protoc-gen-go-spprpc en $PATH y luego ejecutamos protoc para generar el código que queremos.

protoc --go_out=.. --go-spprpc_out=.. HelloService.proto

Debido a que protoc-gen-go-spprpc tiene que depender del protocolo para ejecutarse, es un poco complicado depurar. Podemos usar

fmt.Fprintf(os.Stderr, "Fprintln: %v\n", err)

Para imprimir el registro de errores para depurarlo.

Resumen

Eso es todo lo que hay en este artículo. Primero implementamos una llamada RPC usando protobuf y luego creamos un complemento de protobuf para ayudarnos a generar el código. Esto nos abre la puerta para aprender protobuf RPC y es nuestro camino hacia una comprensión profunda de gRPC. Espero que todos puedan dominar esta tecnología.

Referencia

  1. https://taoshu.in/go/create-protoc-plugin.html
  2. https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-02-pb-intro.html
Declaración de liberación Este artículo se reproduce en: https://dev.to/huizhou92/rpc-action-ep2-using-protobuf-and-creating-a-custom-plugin-2j9j?1 Si hay alguna infracción, comuníquese con Study_golang@163 .com para eliminarlo
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3