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.
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 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 }
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
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 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; }
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 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) }
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 :
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 }
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) } } }
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
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)
To print the error log to debug.
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.
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!