"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > إجراء RPC EPUsing Protobuf وإنشاء مكون إضافي مخصص

إجراء RPC EPUsing Protobuf وإنشاء مكون إضافي مخصص

تم النشر بتاريخ 2024-11-08
تصفح:182

RPC Action EPUsing Protobuf and Creating a Custom Plugin

في المقالة السابقة، قمت بتطبيق واجهة 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

نظام البرنامج المساعد لprotoc

لإنشاء تعليمات برمجية من ملفات 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.

تخصيص البرنامج المساعد protoc

إذا أضفنا توقيت واجهة 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، والتي تقلل بشكل كبير من صعوبة تطوير المكونات الإضافية:

  1. أولاً، قمنا بإنشاء مشروع لغة go، مثل protoc-gen-go-spprpc
  2. ثم نحتاج إلى تحديد protogen.Options، ثم استدعاء أسلوب التشغيل الخاص به، وتمرير رد اتصال خطأ func(*protogen.Plugin). هذه هي نهاية رمز العملية الرئيسية.
  3. يمكننا أيضًا تعيين معلمة ParamFunc لـ protogen.Options، بحيث يقوم protogen تلقائيًا بتحليل المعلمات التي تم تمريرها عبر سطر الأوامر لنا. يتم التعامل مع العمليات مثل قراءة وفك تشفير معلومات البروتوبوف من المدخلات القياسية، وترميز معلومات الإدخال إلى البروتوبوف وكتابة stdout بواسطة البروتوجين. ما يتعين علينا القيام به هو التفاعل مع protogen.Plugin لتنفيذ منطق إنشاء التعليمات البرمجية.

أهم شيء لكل خدمة هو اسم الخدمة، ومن ثم لكل خدمة مجموعة من الطرق. بالنسبة للطريقة التي تحددها الخدمة، فإن الشيء الأكثر أهمية هو اسم الطريقة، وكذلك اسم معلمة الإدخال ونوع معلمة الإخراج. دعونا أولاً نحدد 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. أتمنى أن يتمكن الجميع من إتقان هذه التكنولوجيا.

مرجع

  1. https://taoshu.in/go/create-protoc-plugin.html
  2. https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-02-pb-intro.html
بيان الافراج تم إعادة نشر هذه المقالة على: https://dev.to/huizhou92/rpc-action-ep2-using-protobuf-and-creating-a-custom-plugin-2j9j?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ Study_golang@163 .com لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3