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.
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
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.
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; }
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.
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:
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) } } }
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.
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.
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