1. مقدمة
في هذا الدرس العملي، ستستخدم gRPC-Go لإنشاء عميل وخادم يشكّلان أساس تطبيق لربط المسارات مكتوب بلغة Go.
في نهاية هذا البرنامج التعليمي، سيكون لديك تطبيق عميل يتصل بخادم بعيد باستخدام gRPC للحصول على اسم أو عنوان بريدي للموقع الجغرافي الذي يقع عند إحداثيات معيّنة على الخريطة. قد يستخدم تطبيق متكامل تصميم العميل والخادم هذا لتعداد نقاط الاهتمام أو تلخيصها على طول مسار معيّن.
يتم تحديد الخدمة في ملف Protocol Buffers، وسيتم استخدام هذا الملف لإنشاء رمز نموذجي للبرنامج العميل والخادم حتى يتمكّنا من التواصل مع بعضهما البعض، ما يوفّر عليك الوقت والجهد في تنفيذ هذه الوظيفة.
لا يهتم هذا الرمز الذي تم إنشاؤه بتعقيدات الاتصال بين الخادم والعميل فحسب، بل أيضًا بتسلسل البيانات وإلغاء تسلسلها.
أهداف الدورة التعليمية
- كيفية استخدام مخزن البروتوكولات المؤقت لتحديد واجهة برمجة تطبيقات الخدمة
- كيفية إنشاء برنامج عميل وخادم يستندان إلى gRPC من تعريف Protocol Buffers باستخدام إنشاء الرموز البرمجية المبرمَج
- فهم عملية التواصل بين العميل والخادم باستخدام gRPC
هذا الدرس التطبيقي حول الترميز موجّه لمطوّري Go الجدد على gRPC أو الذين يريدون مراجعة gRPC، أو أي شخص آخر مهتم بإنشاء أنظمة موزّعة. لا يُشترط توفّر خبرة سابقة في gRPC.
2. قبل البدء
المتطلبات الأساسية
تأكَّد من تثبيت ما يلي:
- الإصدار 1.24.5 أو الإصدارات الأحدث من سلسلة أدوات Go للحصول على تعليمات التثبيت، يُرجى الاطّلاع على دليل البدء في Go.
- برنامج تجميع البيانات المنظّمة
protoc
، الإصدار 3.27.1 أو إصدار أحدث للحصول على تعليمات التثبيت، يُرجى الاطّلاع على دليل التثبيت الخاص بالمترجم. - مكوّنات إضافية لبرنامج تحويل مخزن البروتوكولات المؤقت إلى Go وgRPC لتثبيت هذه المكوّنات الإضافية، شغِّل الأوامر التالية:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
عدِّل المتغيّر PATH
ليتمكّن برنامج تجميع بروتوكول المخزن المؤقت من العثور على المكوّنات الإضافية:
export PATH="$PATH:$(go env GOPATH)/bin"
الحصول على الشفرة
ولكي لا تضطر إلى البدء من الصفر تمامًا، يوفّر لك هذا الدرس التطبيقي حول الترميز بنية أساسية لرمز المصدر الخاص بالتطبيق لتتمكّن من إكماله. ستوضّح لك الخطوات التالية كيفية إكمال التطبيق، بما في ذلك استخدام مكوّنات برنامج تجميع بروتوكول المخزن المؤقت لإنشاء رمز gRPC النموذجي.
نزِّل الرمز المصدري هذا كأرشيف ZIP من GitHub وفكّ ضغط محتواه.
بدلاً من ذلك، يتوفّر رمز المصدر المكتمل على GitHub إذا كنت تريد تخطّي كتابة عملية التنفيذ.
3- تحديد الخدمة
تتمثّل خطوتك الأولى في تحديد خدمة gRPC للتطبيق وطريقة RPC وأنواع رسائل الطلبات والاستجابات باستخدام بروتوكول المخازن المؤقتة. ستوفّر خدمتك ما يلي:
- طريقة استدعاء إجراء عن بُعد (RPC) تُسمّى
GetFeature
ينفّذها الخادم ويستدعيها العميل. - نوعا الرسائل
Point
وFeature
اللذان يمثّلان بنى البيانات المتبادلة بين العميل والخادم عند استخدام طريقةGetFeature
يقدّم العميل إحداثيات الخريطة كـPoint
في طلبGetFeature
إلى الخادم، ويردّ الخادم بـFeature
مطابق يصف أي شيء يقع في تلك الإحداثيات.
سيتم تحديد طريقة RPC هذه وأنواع الرسائل الخاصة بها في ملف routeguide/route_guide.proto
الخاص برمز المصدر المقدَّم.
يُشار إلى Protocol Buffers عادةً باسم protobufs. لمزيد من المعلومات عن مصطلحات gRPC، يُرجى الاطّلاع على المفاهيم الأساسية والبنية ودورة الحياة في gRPC.
أنواع الرسائل
في ملف routeguide/route_guide.proto
الخاص بالرمز المصدر، حدِّد أولاً نوع الرسالة Point
. يمثّل Point
زوج إحداثيات خط العرض وخط الطول على الخريطة. في هذا الدرس العملي، استخدِم أعدادًا صحيحة للإحداثيات:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
الرقمان 1
و2
هما رقما تعريف فريدان لكل حقل من الحقول في بنية message
.
بعد ذلك، حدِّد Feature
نوع الرسالة. يستخدم Feature
الحقل string
للاسم أو العنوان البريدي الخاص بمكان محدّد من خلال Point
:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
طريقة الخدمة
يحتوي ملف route_guide.proto
على بنية service
باسم RouteGuide
تحدّد طريقة واحدة أو أكثر توفّرها خدمة التطبيق.
أضِف الطريقة rpc
داخل تعريف RouteGuide
.GetFeature
كما هو موضّح سابقًا، ستبحث هذه الطريقة عن اسم أو عنوان موقع جغرافي من مجموعة إحداثيات معيّنة، لذا اطلب من GetFeature
عرض Feature
لـ Point
معيّن:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
هذه طريقة أحادية لاستدعاء الإجراء عن بُعد: استدعاء إجراء بسيط عن بُعد يرسل فيه العميل طلبًا إلى الخادم وينتظر تلقّي ردّ، تمامًا مثل استدعاء دالة محلية.
4. إنشاء رمز العميل والخادم
بعد ذلك، أنشئ رمز gRPC النموذجي لكل من البرنامج الخادم والبرنامج العميل من ملف .proto
باستخدام برنامج تجميع مخزن البروتوكولات المؤقت. في الدليل routeguide
، نفِّذ ما يلي:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
ينشئ هذا الأمر الملفات التالية:
-
route_guide.pb.go
، الذي يحتوي على دوال لإنشاء أنواع رسائل التطبيق والوصول إلى بياناتها. route_guide_grpc.pb.go
، الذي يحتوي على دوال يستخدمها العميل لاستدعاء طريقة gRPC البعيدة للخدمة، ودوال يستخدمها الخادم لتقديم تلك الخدمة البعيدة
بعد ذلك، سننفّذ طريقة GetFeature
من جهة الخادم، حتى يتمكّن الخادم من الردّ على الطلب الذي يرسله العميل.
5- تنفيذ الخدمة
يتم تنفيذ العمل الرئيسي من خلال الدالة GetFeature
على جانب الخادم: تأخذ هذه الدالة رسالة Point
من العميل وتعرض في رسالة Feature
معلومات الموقع الجغرافي المطابقة من قائمة بالأماكن المعروفة. في ما يلي طريقة تنفيذ الدالة في server/server.go
:
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
for _, feature := range s.savedFeatures {
if proto.Equal(feature.Location, point) {
return feature, nil
}
}
// No feature was found, return an unnamed feature
return &pb.Feature{Location: point}, nil
}
عند استدعاء هذه الطريقة بعد تلقّي طلب من برنامج عميل بعيد، يتم تمرير عنصر Context
يصف عملية استدعاء الإجراء عن بُعد، وعنصر Point
مخزن بروتوكولات مؤقت من طلب العميل هذا. تعرض الدالة عنصرًا من بروتوكول Feature
للموقع الجغرافي الذي تم البحث عنه وerror
حسب الحاجة.
في الطريقة، املأ عنصر Feature
بالمعلومات المناسبة لـ Point
المحدّدة، ثم return
مع خطأ nil
لإعلام gRPC بأنّك انتهيت من التعامل مع طلب الإجراء عن بُعد (RPC) وأنّه يمكن إرجاع عنصر Feature
إلى العميل.
تتطلّب الطريقة GetFeature
إنشاء كائن routeGuideServer
وتسجيله حتى يمكن توجيه الطلبات من العملاء لعمليات البحث عن الموقع الجغرافي إلى هذه الدالة. يتم ذلك في main()
:
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
s := &routeGuideServer{}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)
}
في ما يلي الخطوات التي تحدث في main()
:
- حدِّد منفذ TCP الذي سيتم استخدامه للاستماع إلى طلبات العميل البعيد باستخدام
lis, err := net.Listen(...)
. تستخدم التطبيقات تلقائيًا منفذ TCP رقم50051
كما هو محدّد في المتغيّرport
أو من خلال تمرير الخيار--port
في سطر الأوامر عند تشغيل الخادم. إذا تعذّر فتح منفذ TCP، ينتهي التطبيق بحدوث خطأ فادح. - أنشئ مثيلاً لخادم gRPC باستخدام
grpc.NewServer(...)
، وسمِّ هذا المثيلgrpcServer
. - أنشئ مؤشرًا إلى
routeGuideServer
، وهو بنية تمثّل خدمة واجهة برمجة التطبيقات للتطبيق، وسمِّ المؤشرs.
- استخدِم
s.loadFeatures()
لملء المصفوفةs.savedFeatures
بالمواقع الجغرافية التي يمكن البحث عنها من خلالGetFeature
. - سجِّل خدمة واجهة برمجة التطبيقات مع خادم gRPC ليتم توجيه طلبات استدعاء الإجراء عن بُعد (RPC) إلى
GetFeature
إلى الدالة المناسبة. - اتّصِل بـ
Serve()
على الخادم مع تفاصيل المنفذ لإجراء انتظار حظر لطلبات العميل، ويستمر ذلك إلى أن يتم إيقاف العملية أو يتم استدعاءStop()
.
تستمدّ الدالة loadFeatures()
عمليات الربط بين الإحداثيات والمواقع الجغرافية من server/testdata.go
.
6. إنشاء العميل
عدِّل الآن client/client.go
، وهو المكان الذي ستنفّذ فيه رمز العميل.
لاستدعاء طرق الخدمة البعيدة، علينا أولاً إنشاء قناة gRPC للتواصل مع الخادم. ننشئ ذلك من خلال تمرير سلسلة URI المستهدَفة للخادم (وهي في هذه الحالة ببساطة العنوان ورقم المنفذ) إلى grpc.NewClient()
في دالة main()
الخاصة بالعميل على النحو التالي:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
عنوان الخادم، الذي يحدّده المتغيّر serverAddr
، هو localhost:50051
تلقائيًا، ويمكن تجاوزه باستخدام الخيار --addr
في سطر الأوامر عند تشغيل العميل.
إذا كان العميل بحاجة إلى الاتصال بخدمة تتطلّب بيانات اعتماد المصادقة، مثل بيانات اعتماد TLS أو JWT، يمكن للعميل تمرير كائن DialOptions
كمعلَمة إلى grpc.NewClient
يحتوي على بيانات الاعتماد المطلوبة. لا تتطلّب خدمة RouteGuide
أي بيانات اعتماد.
بعد إعداد القناة في gRPC، نحتاج إلى رمز بديل للعميل من أجل تنفيذ استدعاءات الإجراء عن بُعد (RPC) من خلال استدعاءات دوال Go. نحصل على الرمز الصوري هذا باستخدام طريقة NewRouteGuideClient
التي يوفّرها ملف route_guide_grpc.pb.go
الذي تم إنشاؤه من ملف .proto
الخاص بالتطبيق.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
طُرق خدمة المكالمات
في gRPC-Go، تعمل طلبات RPC في وضع الحظر/التزامن، ما يعني أنّ طلب RPC ينتظر استجابة الخادم، وسيعرض إما استجابة أو خطأ.
Simple RPC
إنّ استدعاء إجراء RPC بسيط GetFeature
هو أمر مباشر تقريبًا مثل استدعاء إجراء محلي، وهو client.GetFeature
في هذه الحالة:
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
يستدعي العميل الطريقة على الرمز التجريبي الذي تم إنشاؤه سابقًا. بالنسبة إلى مَعلمات الطريقة، ينشئ العميل عنصرًا من بروتوكول Point
لطلب المخزن المؤقت ويملأه. يمكنك أيضًا تمرير عنصر context.Context
يتيح لنا تغيير سلوك RPC إذا لزم الأمر، مثل تحديد حد زمني للمكالمة أو إلغاء RPC قيد التنفيذ. إذا لم تعرض المكالمة خطأً، يمكن للعميل قراءة معلومات الاستجابة من الخادم من القيمة المرجَعة الأولى:
log.Println(feature)
في المجمل، يجب أن تبدو الدالة main()
الخاصة بالعميل على النحو التالي:
func main() {
flag.Parse()
// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
// Create a new RouteGuide stub.
client := pb.NewRouteGuideClient(conn)
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
log.Println(feature)
}
7. جرّبه الآن
تأكَّد من أنّ الخادم والعميل يعملان معًا بشكل صحيح من خلال تنفيذ الأوامر التالية في دليل العمل الخاص بالتطبيق:
- شغِّل الخادم في إحدى الوحدات الطرفية:
cd server go run .
- شغِّل العميل من وحدة طرفية أخرى:
cd client go run .
ستظهر لك نتيجة مشابهة لما يلي، مع حذف الطوابع الزمنية لتوضيح الصورة:
Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
8. الخطوات التالية
- تعرَّف على طريقة عمل gRPC في مقدمة عن gRPC والمفاهيم الأساسية
- اتّبِع الخطوات الواردة في الدليل التعليمي الخاص بالأساسيات
- استكشاف مرجع واجهة برمجة التطبيقات