1. مقدمة
في هذا الدرس التطبيقي حول الترميز، ستستخدم gRPC-Java لإنشاء برنامج خادم وبرنامج عميل يشكّلان أساس تطبيق لربط المسارات مكتوب بلغة Java.
في نهاية هذا البرنامج التعليمي، سيكون لديك تطبيق عميل يتصل بخادم بعيد باستخدام gRPC للحصول على اسم أو عنوان بريدي للموقع الجغرافي الذي يقع عند إحداثيات معيّنة على الخريطة. قد يستخدم تطبيق متكامل تصميم العميل والخادم هذا لتعداد نقاط الاهتمام أو تلخيصها على طول مسار معيّن.
يتم تحديد واجهة برمجة التطبيقات الخاصة بالخادم في ملف Protocol Buffers، وسيتم استخدام هذا الملف لإنشاء رمز نموذجي لكل من العميل والخادم حتى يتمكّنا من التواصل مع بعضهما، ما يوفّر عليك الوقت والجهد في تنفيذ هذه الوظيفة.
لا يهتم هذا الرمز الذي تم إنشاؤه بتعقيدات الاتصال بين الخادم والعميل فحسب، بل أيضًا بتسلسل البيانات وإلغاء تسلسلها.
أهداف الدورة التعليمية
- كيفية استخدام مخزن البروتوكولات المؤقت لتحديد واجهة برمجة تطبيقات الخدمة
- كيفية إنشاء برنامج عميل وخادم يستندان إلى gRPC من تعريف Protocol Buffers باستخدام إنشاء الرموز البرمجية المبرمَج
- فهم عملية التواصل بين العميل والخادم باستخدام gRPC
هذا الدرس التطبيقي حول الترميز مخصّص لمطوّري Java الجدد في gRPC أو الذين يريدون مراجعة gRPC، أو أي شخص آخر مهتم بإنشاء أنظمة موزّعة. لا يُشترط توفّر خبرة سابقة في gRPC.
2. قبل البدء
المتطلبات الأساسية
- الإصدار 8 أو الإصدارات الأحدث من JDK
الحصول على الشفرة
ولكي لا تضطر إلى البدء من الصفر تمامًا، يوفّر لك هذا الدرس التطبيقي حول الترميز بنية أساسية لرمز المصدر الخاص بالتطبيق لتتمكّن من إكماله. ستوضّح لك الخطوات التالية كيفية إكمال التطبيق، بما في ذلك استخدام مكوّنات برنامج تجميع بروتوكول المخزن المؤقت لإنشاء رمز gRPC النموذجي.
أولاً، أنشئ دليل عمل الدرس التطبيقي وادخله:
mkdir grpc-java-getting-started && cd grpc-java-getting-started
نزِّل الدرس التطبيقي حول الترميز واستخرِجه:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-java-getting-started/start_here
بدلاً من ذلك، يمكنك تنزيل ملف .zip الذي يحتوي على دليل الدرس العملي فقط وفك ضغطه يدويًا.
يتوفّر رمز المصدر المكتمل على GitHub إذا أردت تخطّي كتابة عملية التنفيذ.
3- تحديد الخدمة
تتمثّل خطوتك الأولى في تحديد خدمة gRPC للتطبيق وطريقة RPC وأنواع رسائل الطلبات والاستجابات باستخدام بروتوكول المخازن المؤقتة. ستوفّر خدمتك ما يلي:
- طريقة استدعاء إجراء عن بُعد (RPC) تُسمّى
GetFeature
ينفّذها الخادم ويستدعيها العميل. - نوعا الرسائل
Point
وFeature
اللذان يمثّلان بنى البيانات المتبادلة بين العميل والخادم عند استخدام طريقةGetFeature
يقدّم العميل إحداثيات الخريطة كـPoint
في طلبGetFeature
إلى الخادم، ويردّ الخادم بـFeature
مطابق يصف أي شيء يقع في تلك الإحداثيات.
سيتم تحديد طريقة RPC هذه وأنواع الرسائل الخاصة بها في ملف src/main/proto/routeguide/route_guide.proto
الخاص برمز المصدر المقدَّم.
يُشار إلى Protocol Buffers عادةً باسم protobufs. لمزيد من المعلومات عن مصطلحات gRPC، يُرجى الاطّلاع على المفاهيم الأساسية والبنية ودورة الحياة في gRPC.
بما أنّنا ننشئ رمز Java في هذا المثال، حدّدنا خيار الملف java_package
واسمًا لفئة Java في .proto
:
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
أنواع الرسائل
في ملف 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
. وننفّذ ذلك باستخدام برنامج تجميع بروتوكول buffer protoc
مع مكوّن إضافي خاص بلغة Java في gRPC. عليك استخدام برنامج التجميع proto3 (الذي يتوافق مع كل من بنية proto2 وproto3) من أجل إنشاء خدمات gRPC.
عند استخدام Gradle أو Maven، يمكن لمكوّن protoc
الإضافي للإنشاء إنشاء الرمز البرمجي اللازم كجزء من عملية الإنشاء. يمكنك الرجوع إلى ملف README الخاص بـ grpc-java لمعرفة كيفية إنشاء الرمز من ملفات .proto
الخاصة بك.
لقد وفّرنا بيئة Gradle وإعداداتها في رمز المصدر الخاص بالتجربة العملية لإنشاء هذا المشروع.
داخل دليل grpc-java-getting-started
، نفِّذ الأمر التالي:
$ chmod +x gradlew $ ./gradlew generateProto
يتم إنشاء الفئات التالية من تعريف الخدمة:
Feature.java
وPoint.java
وغيرها التي تحتوي على جميع رموز المخزن المؤقت للبروتوكول لتعبئة أنواع رسائل الطلبات والردود وتسلسلها واستردادهاRouteGuideGrpc.java
الذي يحتوي (بالإضافة إلى بعض الرموز المفيدة الأخرى) على فئة أساسية للخوادمRouteGuide
لتنفيذها،RouteGuideGrpc.RouteGuideImplBase
، مع جميع الطرق المحددة في خدمةRouteGuide
وفئات التعليمات البرمجية البديلة للعملاء لاستخدامها.
5- تنفيذ الخادم
لنلقِ نظرة أولاً على كيفية إنشاء خادم RouteGuide
. تتضمّن عملية إنجاز خدمة RouteGuide
لمهامها جزأين:
- تنفيذ واجهة الخدمة التي تم إنشاؤها من تعريف الخدمة، والتي تنفّذ "العمل" الفعلي لخدمتنا
- تشغيل خادم gRPC للاستماع إلى الطلبات من العملاء وإرسالها إلى تنفيذ الخدمة المناسب
تنفيذ RouteGuide
كما ترى، يحتوي الخادم على فئة RouteGuideService
توسّع الفئة المجردة RouteGuideGrpc.RouteGuideImplBase
التي تم إنشاؤها:
private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}
لقد قدّمنا الملفَّين التاليَين لتهيئة الخادم بالميزات:
./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
لنلقِ نظرة على عملية تنفيذ بسيطة لبروتوكول RPC بالتفصيل.
Unary RPC
تنفّذ RouteGuideService
جميع طرق الخدمة لدينا. في هذه الحالة، يكون الرمز هو GetFeature()
فقط، ويستقبل رسالة Point
من العميل ويعرض في رسالة Feature
معلومات الموقع الجغرافي المطابقة من قائمة الأماكن المعروفة.
@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
responseObserver.onNext(checkFeature(request));
responseObserver.onCompleted();
}
تتطلّب الطريقة getFeature()
مَعلمتَين:
Point
: الطلب-
StreamObserver<Feature>
: أداة مراقبة الاستجابة، وهي واجهة خاصة يمكن للخادم استخدامها للاتصال بها عند تلقّي استجابته.
لعرض ردّنا على العميل وإكمال المكالمة، اتّبِع الخطوات التالية:
- ننشئ عنصر استجابة
Feature
ونملأه لإرجاعه إلى العميل، كما هو محدّد في تعريف الخدمة. في هذا المثال، يتم ذلك في طريقةcheckFeature()
خاصة منفصلة. - نستخدم طريقة
onNext()
في مراقب الاستجابة لعرضFeature
. - نستخدم طريقة
onCompleted()
في مراقب الاستجابة لتحديد أنّنا انتهينا من التعامل مع إجراء RPC.
بدء الخادم
بعد تنفيذ جميع طرق الخدمة، علينا بدء تشغيل خادم gRPC حتى يتمكّن العملاء من استخدام خدمتنا. نضمِّن في الرمز النموذجي إنشاء الكائن ServerBuilder:
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
ننشئ الخدمة في الدالة الإنشائية:
- حدِّد المنفذ الذي نريد استخدامه للاستماع إلى طلبات العميل باستخدام طريقة
forPort()
الخاصة بأداة الإنشاء (سيتم استخدام عنوان أحرف البدل). - أنشئ مثيلاً لفئة تنفيذ الخدمة
RouteGuideService
وأدخِله في طريقةaddService()
الخاصة بأداة الإنشاء. - استدعِ الدالة
build()
في أداة الإنشاء لإنشاء خادم استدعاء إجراء عن بُعد (RPC) لخدمتنا.
يوضّح المقتطف التالي كيفية إنشاء عنصر ServerBuilder
.
/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
port, RouteGuideUtil.parseFeatures(featureFile));
}
يوضّح المقتطف التالي كيفية إنشاء عنصر خادم لخدمة RouteGuide
.
/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
this.port = port;
server = serverBuilder.addService(new RouteGuideService(features))
.build();
}
نفِّذ طريقة بدء تستدعي start
على الخادم الذي أنشأناه أعلاه.
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
نفِّذ طريقة لانتظار اكتمال الخادم حتى لا يتم الخروج منه على الفور.
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
كما ترى، ننشئ خادمنا ونبدأ تشغيله باستخدام ServerBuilder
.
في الطريقة الرئيسية، ننفّذ ما يلي:
- أنشئ مثيلاً من
RouteGuideServer
. - اتّصِل على
start()
لتفعيل خادم استدعاء إجراء عن بُعد (RPC) لخدمتنا. - انتظِر إلى أن يتم إيقاف الخدمة من خلال الاتصال بالرقم
blockUntilShutdown()
.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. إنشاء العميل
في هذا القسم، سنتعرّف على كيفية إنشاء برنامج لخدمة RouteGuide
.
إنشاء نسخة من عنصر زائف
لاستدعاء طرق الخدمة، يجب أولاً إنشاء رمز بديل. هناك نوعان من الرموز الصورية، ولكننا نحتاج إلى استخدام الرمز الصوري الحاصِر فقط في هذا الدرس العملي. وهما:
- رمز حظر/تزامن يطلب إجراء مكالمة RPC وينتظر رد الخادم، ثم يعرض إما استجابة أو يثير استثناءً.
- دالة غير حظر/غير متزامنة تنفّذ طلبات غير حظر إلى الخادم، ويتم عرض الردّ بشكل غير متزامن. يمكنك إجراء أنواع معيّنة من مكالمات البث فقط باستخدام رمز stub غير متزامن.
علينا أولاً إنشاء قناة gRPC، ثم استخدام القناة لإنشاء عنصر نائب.
كان بإمكاننا استخدام ManagedChannelBuilder
مباشرةً لإنشاء القناة.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
ولكن لنستخدِم طريقة مساعدة تأخذ سلسلة تحتوي على hostname:port
.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
يمكننا الآن استخدام القناة لإنشاء رمز حظر مؤقت. في هذا الدرس البرمجي، لا تتوفّر لدينا سوى طلبات RPC الحظر، لذا نستخدم طريقة newBlockingStub
المتوفّرة في فئة RouteGuideGrpc
التي أنشأناها من .proto
.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
طُرق خدمة المكالمات
لنلقِ نظرة الآن على كيفية استدعاء طرق الخدمة.
Simple RPC
إنّ استدعاء إجراء RPC بسيط GetFeature
لا يختلف كثيرًا عن استدعاء إجراء محلي.
ننشئ كائنًا من بروتوكول طلبات مؤقتة ونملأه (في حالتنا Point
)، ثم نمرّره إلى الطريقة getFeature()
في رمز الحظر المؤقت، ونحصل على Feature
.
في حال حدوث خطأ، يتم ترميزه على شكل Status
، ويمكننا الحصول عليه من StatusRuntimeException
.
Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
Feature feature;
try {
feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
تسجّل الرموز النموذجية رسالة تحتوي على المحتوى استنادًا إلى ما إذا كانت هناك ميزة في النقطة المحدّدة أم لا.
7. ننصحكم بتجربتها.
- داخل دليل
start_here
، نفِّذ الأمر التالي:
$ ./gradlew installDist
سيؤدي ذلك إلى تجميع الرمز البرمجي وتعبئته في ملف jar وإنشاء البرامج النصية التي تشغّل المثال. سيتم إنشاؤها في دليل build/install/start_here/bin/
. البرنامجان النصيان هما: route-guide-server
وroute-guide-client
.
يجب أن يكون الخادم قيد التشغيل قبل بدء تشغيل العميل.
- شغِّل الخادم:
$ ./build/install/start_here/bin/route-guide-server
- شغِّل العميل:
$ ./build/install/start_here/bin/route-guide-client
ستظهر لك نتيجة مشابهة لما يلي، مع حذف الطوابع الزمنية لتوضيح الصورة:
INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906 INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619 INFO: *** GetFeature: lat=0 lon=0 INFO: Found no feature at 0, 0
8. الخطوات التالية
- تعرَّف على طريقة عمل gRPC في مقدمة عن gRPC والمفاهيم الأساسية
- اتّبِع الخطوات الواردة في الدليل التعليمي الخاص بالأساسيات
- استكشاف مرجع واجهة برمجة التطبيقات