"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > RPC Action EPUUtilisation de Protobuf et création d'un plugin personnalisé

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

Publié le 2024-09-12
Parcourir:543

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. Le procédé 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;
}

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

protoc --go_out=. hello-service.proto

Ensuite nous modifions 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  
}  

L'utiliser n'est pas différent d'avant, même si 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

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 de protocole 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;
}

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

Cela modifierait notre code d'entreprise pour qu'il ressemble à 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)  
}

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

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. Tout d'abord, nous créons un projet en langage Go, tel que protoc-gen-go-spprpc
  2. Ensuite, nous devons définir un protogen.Options, puis appeler sa méthode Run et transmettre un rappel d'erreur func(*protogen.Plugin). C'est la fin du code du processus principal.
  3. Nous pouvons également définir le paramètre ParamFunc de protogen.Options, afin que protogen analyse automatiquement les paramètres transmis par la ligne de commande pour nous. Les opérations telles que la lecture et le décodage des informations protobuf à partir de l'entrée standard, le codage des informations d'entrée dans protobuf et l'écriture de la sortie standard sont toutes gérées par protogen. Ce que nous devons faire est d'interagir avec protogen.Plugin pour implémenter la logique de génération de code.

La chose la plus importante pour chaque service est le nom du service, puis chaque service dispose d'un ensemble de méthodes. Pour la méthode définie par le service, le plus important est le nom de la méthode, ainsi que le nom du paramètre d'entrée et le type du paramètre de sortie. Définissons d'abord un ServiceData pour décrire les méta-informations du service :

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

Vient ensuite la logique principale, et la logique de génération de code, et enfin l'appel à tmpl pour générer le 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)  
       }  
    }  
}

Plugin de débogage

Enfin, nous mettons le fichier d'exécution binaire compilé protoc-gen-go-spprpc dans $PATH, puis exécutons protoc pour générer le code souhaité.

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

Étant donné que protoc-gen-go-spprpc doit dépendre du protocole pour s'exécuter, il est un peu difficile à déboguer. Nous pouvons utiliser

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

Pour imprimer le journal des erreurs pour déboguer.

Résumé

C'est tout ce qu'il y a à dire dans cet article. Nous avons d'abord implémenté un appel RPC à l'aide de protobuf, puis avons créé un plugin protobuf pour nous aider à générer le code. Cela nous ouvre la porte à l'apprentissage de protobuf RPC et constitue notre chemin vers une compréhension approfondie de gRPC. J'espère que tout le monde pourra maîtriser cette technologie.

Référence

  1. https://taoshu.in/go/create-protoc-plugin.html
  2. https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-02-pb-intro.html
Déclaration de sortie Cet article est reproduit sur : https://dev.to/huizhou92/rpc-action-ep2-using-protobuf-and-creating-a-custom-plugin-2j9j?1 En cas de violation, veuillez contacter study_golang@163 .com pour le supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3