Maison > développement back-end > Golang > le corps du texte

RPC Action EPUUtilisation de Protobuf et création d'un plugin personnalisé

王林
Libérer: 2024-09-09 20:30:10
original
472 Les gens l'ont consulté

RPC Action EPUsing Protobuf and Creating a Custom Plugin

Dans l'article précédent, j'ai implémenté une interface RPC simple à l'aide du package net/rpc et essayé l'encodage Gob fourni avec l'encodage net/rpc et JSON pour apprendre quelques bases de Golang. RPC. Dans cet article, je vais combiner net/rpc avec protobuf et créer mon plugin protobuf pour nous aider à générer du code, alors commençons.

Cet article a été publié pour la première fois dans le plan Medium MPP. Si vous êtes un utilisateur Medium, veuillez me suivre sur Medium. Merci beaucoup.

Nous avons dû utiliser gRPC et protobuf pendant notre travail, mais ils ne sont pas liés. gRPC peut être codé en JSON et protobuf peut être implémenté dans d'autres langages.

Protocol Buffers (Protobuf) est un format de données multiplateforme gratuit et open source utilisé pour sérialiser des données structurées. Il est utile pour développer des programmes qui communiquent entre eux sur un réseau ou pour stocker des données. La méthode implique un langage de description d'interface qui décrit la structure de certaines données et un programme qui génère du code source à partir de cette description pour générer ou analyser un flux d'octets qui représente les données structurées.

Un exemple d'utilisation de protobuf

Nous écrivons d'abord un fichier proto hello-service.proto qui définit un message "String"

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

message String {
  string value = 1;
}
Copier après la connexion

Utilisez ensuite l'utilitaire protoc pour générer le code Go pour le message String

protoc --go_out=. hello-service.proto
Copier après la connexion

Ensuite on modifie les arguments de la fonction Hello pour utiliser la String générée par le fichier protobuf.

type HelloServiceInterface = interface {  
    Hello(request api.String, reply *api.String) error  
}  
Copier après la connexion

L'utiliser n'est pas différent d'avant, même ce n'est pas aussi pratique que d'utiliser directement une chaîne. Alors pourquoi devrions-nous utiliser protobuf ? Comme je l'ai dit plus tôt, utiliser Protobuf pour définir des interfaces et des messages de service RPC indépendants du langage, puis utiliser l'outil protoc pour générer du code dans différentes langues, c'est là que réside sa vraie valeur. Par exemple, utilisez le plugin officiel protoc-gen-go pour générer du code gRPC.

protoc --go_out=plugins=grpc. hello-service.proto
Copier après la connexion

Système de plugin pour le protocole

Pour générer du code à partir de fichiers protobuf, nous devons installer le protocole , mais le protocole ne sait pas quelle est notre langue cible, nous avons donc besoin de plugins pour nous aider à générer du code. comment fonctionne le système de plugins de protocole ? Prenons le grpc ci-dessus comme exemple.

Il y a un paramètre --go_out ici. Puisque le plugin que nous appelons est protoc-gen-go, le paramètre s'appelle go_out ; si le nom était XXX, le paramètre s'appellerait XXX_out.

Lorsque protoc est en cours d'exécution, il analysera d'abord le fichier protobuf et générera un ensemble de données descriptives codées par Protocol Buffers. Il déterminera d'abord si le plugin go est inclus ou non dans protoc, puis il essaiera de rechercher protoc-gen-go dans $PATH, et s'il ne le trouve pas, il signalera une erreur, puis il exécutera protoc-gen-go. protoc-gen-go et envoie les données de description à la commande du plugin via stdin. Une fois que le plugin a généré le contenu du fichier, il entre ensuite les données codées dans les tampons de protocole sur la sortie standard pour indiquer au protocole de générer le fichier spécifique.

plugins=grpc est un plugin fourni avec protoc-gen-go afin de l'invoquer. Si vous ne l'utilisez pas, il générera uniquement un message dans Go, mais vous pouvez utiliser ce plugin pour générer du code lié à grpc.

Personnaliser un plugin de protocole

Si nous ajoutons le timing de l'interface Hello à protobuf, pouvons-nous personnaliser un plugin protoc pour générer du code directement ?

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

Objectif

Pour cet article, mon objectif était de créer un plugin qui serait ensuite utilisé pour générer du code RPC côté serveur et côté client qui ressemblerait à ceci.

// 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)  
}
Copier après la connexion

Cela modifierait notre code d'entreprise pour ressembler à ce qui suit

// 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)  
}
Copier après la connexion

Sur la base du code généré, notre charge de travail est déjà beaucoup plus petite et les risques d'erreur sont déjà très faibles. Un bon début.

Sur la base du code API ci-dessus, nous pouvons extraire un fichier modèle :

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)}  
`
Copier après la connexion

L'ensemble du modèle est clair et il contient des espaces réservés, tels que MethodName, ServiceName, etc., que nous aborderons plus tard.

Comment développer un plug-in ?

Google a publié l'API 1 du langage Go, qui introduit un nouveau package google.golang.org/protobuf/compile R/protogen, qui réduit considérablement la difficulté de développement de plugins :

  1. First of all, we create a go language project, such as protoc-gen-go-spprpc
  2. Then we need to define a protogen.Options, then call its Run method, and pass in a func(*protogen.Plugin) error callback. This is the end of the main process code.
  3. We can also set the ParamFunc parameter of protogen.Options, so that protogen will automatically parse the parameters passed by the command line for us. Operations such as reading and decoding protobuf information from standard input, encoding input information into protobuf and writing stdout are all handled by protogen. What we need to do is to interact with protogen.Plugin to implement code generation logic.

The most important thing for each service is the name of the service, and then each service has a set of methods. For the method defined by the service, the most important thing is the name of the method, as well as the name of the input parameter and the output parameter type. Let's first define a ServiceData to describe the meta information of the service:

// ServiceData 
type ServiceData struct {  
    PackageName string  
    ServiceName string  
    MethodList  []Method  
}
// Method 
type Method struct {  
    MethodName     string  
    InputTypeName  string  
    OutputTypeName string  
}
Copier après la connexion

Then comes the main logic, and the code generation logic, and finally the call to tmpl to generate the code.

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)  
       }  
    }  
}
Copier après la connexion

Debug plugin

Finally, we put the compiled binary execution file protoc-gen-go-spprpc in $PATH, and then run protoc to generate the code we want.

protoc --go_out=.. --go-spprpc_out=.. HelloService.proto
Copier après la connexion

Because protoc-gen-go-spprpc has to depend on protoc to run, it's a bit tricky to debug. We can use

fmt.Fprintf(os.Stderr, "Fprintln: %v\n", err)
Copier après la connexion

To print the error log to debug.

Summary

That's all there is to this article. We first implemented an RPC call using protobuf and then created a protobuf plugin to help us generate the code. This opens the door for us to learn protobuf + RPC, and is our path to a thorough understanding of gRPC. I hope everyone can master this technology.

Reference

  1. https://taoshu.in/go/create-protoc-plugin.html
  2. https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-02-pb-intro.html

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!