1. مقدمة
في هذا الدرس التطبيقي حول الترميز، ستستخدم gRPC-Go لإنشاء عميل وخادم يشكّلان الأساس لتطبيق يحدّد المسارات مكتوب بلغة Go.
في نهاية هذا الدليل التعليمي، سيكون لديك عميل يتصل بخادم بعيد باستخدام gRPC للحصول على اسم أو عنوان بريدي للمكان الذي يقع عند إحداثيات معيّنة على الخريطة. قد يستخدم تطبيق متكامل تصميم العميل والخادم هذا لتعداد نقاط الاهتمام أو تلخيصها على طول مسار معيّن.
يتم تحديد الخدمة في ملف بتنسيق Protocol Buffers، وسيتم استخدام هذا الملف لإنشاء رمز نص نموذجي للعميل والخادم حتى يتمكّنا من التواصل مع بعضهما البعض، ما يوفّر عليك الوقت والجهد في تنفيذ هذه الوظيفة.
لا يهتم هذا الرمز الذي تم إنشاؤه بتعقيدات الاتصال بين الخادم والعميل فحسب، بل أيضًا بتسلسل البيانات وإلغاء تسلسلها.
ماذا ستتعلّم؟
- كيفية استخدام "مخازن البروتوكولات المؤقتة" (Protocol Buffers) لتحديد واجهة برمجة تطبيقات الخدمة
- كيفية إنشاء عميل وخادم يستندان إلى gRPC استنادًا إلى "مخازن البروتوكولات المؤقتة" من خلال إنشاء الرموز البرمجية آليًا
- فهم عملية التواصل بين العميل والخادم باستخدام gRPC
هذا الدرس التطبيقي حول الترميز موجّه لمطوّري Go المبتدئين في 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مطابق يصف أي شيء يقع في تلك الإحداثيات.
سيتم تحديد طريقة "استدعاء الإجراء عن بُعد" هذه وأنواع الرسائل الخاصة بها في ملف routeguide/route_guide.proto الخاص بالرمز المصدر المقدَّم.
يُشار إلى "مخازن البروتوكولات المؤقتة" عادةً باسم 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 تحدّد طريقة واحدة أو أكثر تقدّمها خدمة التطبيق.
أضِف GetFeature (وهي طريقة rpc) داخل تعريف RouteGuide. كما هو موضّح سابقًا، ستبحث هذه الطريقة عن اسم أو عنوان مكان من مجموعة إحداثيات معيّنة، لذا اطلب من 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_streaming/routeguide")
client := pb.NewRouteGuideClient(conn)
طُرق خدمات الاستدعاء
في gRPC-Go، تعمل طلبات استدعاء الإجراء عن بُعد (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 والمفاهيم الأساسية
- اتّبِع الخطوات الواردة في الدليل التعليمي الخاص بالأساسيات
- استكشف مرجع واجهة برمجة التطبيقات