В предыдущей статье я реализовал простой интерфейс RPC, используя пакет net/rpc, и опробовал кодировку Gob, идущую в комплекте с net/rpc, и кодировку JSON, чтобы изучить некоторые основы Golang. РПК. В этом посте я объединим net/rpc с protobuf и создам свой плагин protobuf, который поможет нам генерировать код, так что давайте начнем.
Эта статья была впервые опубликована в плане Medium MPP. Если вы являетесь пользователем Medium, подписывайтесь на меня на Medium. Большое спасибо.
Должно быть, во время работы мы использовали gRPC и protobuf, но они не связаны. gRPC можно закодировать с использованием JSON, а protobuf можно реализовать на других языках.
Protocol Buffers (Protobuf) – это бесплатный кроссплатформенный формат данных с открытым исходным кодом, используемый для сериализации структурированных данных. Это полезно при разработке программ, которые взаимодействуют друг с другом по сети или для хранения данных. Этот метод включает в себя язык описания интерфейса, который описывает структуру некоторых данных, и программу, которая генерирует исходный код на основе этого описания для создания или анализа потока байтов, представляющего структурированные данные.
Сначала мы пишем прото-файл hello-service.proto, который определяет сообщение «String»
syntax = "proto3"; package api; option go_package="api"; message String { string value = 1; }
Затем используйте утилиту protoc для создания кода Go для строки сообщения
protoc --go_out=. hello-service.proto
Затем мы модифицируем аргументы функции Hello, чтобы использовать строку, сгенерированную файлом protobuf.
type HelloServiceInterface = interface { Hello(request api.String, reply *api.String) error }
Использование ничем не отличается от предыдущего, даже не так удобно, как непосредственное использование строки. Так почему же нам следует использовать protobuf? Как я уже говорил ранее, использование Protobuf для определения языково-независимых интерфейсов и сообщений службы RPC, а затем использование инструмента protoc для генерации кода на разных языках — вот в чем его реальная ценность. Например, используйте официальный плагин protoc-gen-go для генерации кода gRPC.
protoc --go_out=plugins=grpc. hello-service.proto
Чтобы генерировать код из файлов protobuf, мы должны установить protoc , но протокол не знает, какой у нас целевой язык, поэтому нам нужны плагины, которые помогут нам генерировать код. как работает система плагинов protoc? В качестве примера возьмите приведенный выше grpc.
Здесь есть параметр --go_out. Поскольку плагин, который мы вызываем, — protoc-gen-go, параметр называется go_out; если бы имя было XXX, параметр назывался бы XXX_out.
Когда протокол protoc запущен, он сначала анализирует файл protobuf и генерирует набор описательных данных, закодированных в протокольных буферах. Сначала он определит, включен ли плагин go в protoc, а затем попытается найти protoc-gen-go в $PATH, и если он не сможет его найти, он сообщит об ошибке, а затем запустит protoc-gen-go. protoc-gen-go и отправляет данные описания команде плагина через стандартный ввод. После того, как плагин сгенерирует содержимое файла, он затем вводит данные, закодированные в буфере протокола, на стандартный вывод, чтобы сообщить protoc о необходимости создания конкретного файла.
плагины=grpc — это плагин, который поставляется с protoc-gen-go для его вызова. Если вы его не используете, он будет генерировать только сообщение в Go, но вы можете использовать этот плагин для генерации кода, связанного с grpc.
Если мы добавим синхронизацию интерфейса Hello в protobuf, сможем ли мы настроить плагин protoc для прямой генерации кода?
syntax = "proto3"; package api; option go_package="./api"; service HelloService { rpc Hello (String) returns (String) {} } message String { string value = 1; }
В этой статье моей целью было создать плагин, который затем можно было бы использовать для генерации кода на стороне сервера и клиента RPC, который выглядел бы примерно так.
// 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) }
Это изменит наш бизнес-код и будет выглядеть следующим образом
// 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) }
Исходя из сгенерированного кода, наша рабочая нагрузка уже намного меньше и вероятность ошибки уже очень мала. Хорошее начало.
На основе приведенного выше API-кода мы можем получить файл шаблона:
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)} `
Весь шаблон понятен, и в нем есть несколько заполнителей, таких как имя метода, имя службы и т. д., о которых мы поговорим позже.
Google выпустил API 1 языка Go, в котором представлен новый пакет google.golang.org/protobuf/compile R/protogen, который значительно снижает сложность разработки плагинов:
Самое важное для каждого сервиса — это имя сервиса, а затем у каждого сервиса есть набор методов. Для метода, определенного службой, наиболее важным является имя метода, а также имя входного параметра и тип выходного параметра. Давайте сначала определим ServiceData для описания метаинформации сервиса:
// ServiceData type ServiceData struct { PackageName string ServiceName string MethodList []Method } // Method type Method struct { MethodName string InputTypeName string OutputTypeName string }
Затем следует основная логика, логика генерации кода и, наконец, вызов tmpl для генерации кода.
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) } } }
Наконец, мы помещаем скомпилированный двоичный исполняемый файл protoc-gen-go-spprpc в $PATH, а затем запускаем protoc для генерации нужного кода.
protoc --go_out=.. --go-spprpc_out=.. HelloService.proto
Поскольку запуск protoc-gen-go-spprpc должен зависеть от protoc, его немного сложно отладить. Мы можем использовать
fmt.Fprintf(os.Stderr, "Fprintln: %v\n", err)
Чтобы распечатать журнал ошибок для отладки.
Это все, что есть в этой статье. Сначала мы реализовали вызов RPC с использованием protobuf, а затем создали плагин protobuf, который поможет нам сгенерировать код. Это открывает нам возможность изучить protobuf RPC и является нашим путем к глубокому пониманию gRPC. Я надеюсь, что каждый сможет освоить эту технологию.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3