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