في المقالة السابقة، قمت بتطبيق واجهة RPC بسيطة باستخدام حزمة net/rpc وجربت تشفير Gob الذي يأتي مع تشفير net/rpc وJSON لتعلم بعض أساسيات Golang RPC. في هذا المنشور، سأقوم بدمج net/rpc مع protobuf وإنشاء ملحق protobuf الخاص بي لمساعدتنا في إنشاء التعليمات البرمجية، لذلك دعونا نبدأ.
تم نشر هذه المقالة لأول مرة في خطة MPP المتوسطة. إذا كنت من مستخدمي Medium، فيرجى متابعتي على Medium. شكراً جزيلاً.
لا بد أننا استخدمنا gRPC وprotobuf أثناء عملنا، لكنهما غير ملزمين. يمكن تشفير gRPC باستخدام JSON، ويمكن تنفيذ protobuf بلغات أخرى.
مخازن البروتوكول المؤقتة (Protobuf) هو تنسيق بيانات مجاني ومفتوح المصدر ومتعدد الأنظمة الأساسية يُستخدم لتسلسل البيانات المنظمة. وهو مفيد في تطوير البرامج التي تتواصل مع بعضها البعض عبر الشبكة أو لتخزين البيانات. تتضمن الطريقة لغة وصف واجهة تصف بنية بعض البيانات وبرنامجًا يُنشئ كود المصدر من هذا الوصف لإنشاء أو تحليل دفق من البايتات التي تمثل البيانات المنظمة.
أولاً نكتب ملفًا أوليًا hello-service.proto يحدد الرسالة "سلسلة"
syntax = "proto3"; package api; option go_package="api"; message String { string value = 1; }
ثم استخدم الأداة المساعدة protoc لإنشاء رمز Go للرسالة String
protoc --go_out=. hello-service.proto
ثم نقوم بتعديل وسائط وظيفة Hello لاستخدام السلسلة التي تم إنشاؤها بواسطة ملف protobuf.
type HelloServiceInterface = interface { Hello(request api.String, reply *api.String) error }
لا يختلف استخدامه عن ذي قبل، حتى أنه ليس مناسبًا مثل استخدام السلسلة مباشرة. فلماذا نستخدم البروتوبوف؟ كما قلت سابقًا، استخدام Protobuf لتحديد واجهات ورسائل خدمة RPC المستقلة عن اللغة، ثم استخدام أداة protoc لإنشاء تعليمات برمجية بلغات مختلفة، هو المكان الذي تكمن فيه قيمته الحقيقية. على سبيل المثال، استخدم البرنامج المساعد الرسمي protoc-gen-go لإنشاء كود gRPC.
protoc --go_out=plugins=grpc. hello-service.proto
لإنشاء تعليمات برمجية من ملفات protobuf، يجب علينا تثبيت protoc، لكن 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 ويرسل بيانات الوصف إلى أمر البرنامج المساعد عبر stdin. بعد أن يقوم المكون الإضافي بإنشاء محتويات الملف، يقوم بعد ذلك بإدخال البيانات المشفرة لمخازن البروتوكول المؤقتة إلى stdout لإخبار protoc بإنشاء الملف المحدد.
plugins=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) }
استنادًا إلى الكود الذي تم إنشاؤه، أصبح عبء العمل لدينا أصغر بكثير بالفعل وفرص الخطأ ضئيلة جدًا بالفعل. بداية جيدة.
استنادا إلى رمز واجهة برمجة التطبيقات أعلاه، يمكننا سحب ملف القالب:
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 واجهة برمجة تطبيقات لغة Go 1، والتي تقدم حزمة جديدة 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