gRPC-Go का इस्तेमाल शुरू करना - स्ट्रीमिंग

1. परिचय

इस कोडलैब में, gRPC-Go का इस्तेमाल करके एक क्लाइंट और सर्वर बनाया जाएगा. ये दोनों, Go में लिखे गए रूट-मैपिंग ऐप्लिकेशन की बुनियादी संरचना तैयार करते हैं.

ट्यूटोरियल के आखिर तक, आपके पास एक ऐसा क्लाइंट होगा जो gRPC का इस्तेमाल करके, रिमोट सर्वर से कनेक्ट होता है. इससे क्लाइंट के रूट पर मौजूद सुविधाओं के बारे में जानकारी मिलती है, क्लाइंट के रूट की खास जानकारी बनाई जाती है, और सर्वर और अन्य क्लाइंट के साथ रूट की जानकारी शेयर की जाती है. जैसे, ट्रैफ़िक अपडेट.

सेवा को प्रोटोकॉल बफ़र फ़ाइल में तय किया जाता है. इसका इस्तेमाल क्लाइंट और सर्वर के लिए बॉयलरप्लेट कोड जनरेट करने के लिए किया जाएगा, ताकि वे एक-दूसरे से कम्यूनिकेट कर सकें. इससे आपको उस सुविधा को लागू करने में समय और मेहनत नहीं करनी पड़ेगी.

जनरेट किया गया यह कोड, सर्वर और क्लाइंट के बीच कम्यूनिकेशन की जटिलताओं के साथ-साथ डेटा के क्रमबद्ध और क्रम से हटाने की प्रोसेस को भी मैनेज करता है.

आपको क्या सीखने को मिलेगा

  • किसी सेवा के एपीआई को तय करने के लिए, प्रोटोकॉल बफ़र का इस्तेमाल कैसे करें.
  • ऑटोमेटेड कोड जनरेशन का इस्तेमाल करके, Protocol Buffers की परिभाषा से gRPC पर आधारित क्लाइंट और सर्वर बनाने का तरीका.
  • gRPC के साथ क्लाइंट-सर्वर स्ट्रीमिंग कम्यूनिकेशन के बारे में जानकारी.

यह कोडलैब, Go डेवलपर के लिए है. यह उन लोगों के लिए भी है जो gRPC का इस्तेमाल पहली बार कर रहे हैं या gRPC के बारे में फिर से जानकारी पाना चाहते हैं. इसके अलावा, यह उन लोगों के लिए भी है जो डिस्ट्रिब्यूटेड सिस्टम बनाने में दिलचस्पी रखते हैं. इसके लिए, gRPC का अनुभव होना ज़रूरी नहीं है.

2. शुरू करने से पहले

ज़रूरी शर्तें

पक्का करें कि आपने ये इंस्टॉल किए हों:

  • Go टूलचेन का वर्शन 1.24.5 या उसके बाद का वर्शन. इंस्टॉल करने के निर्देशों के लिए, 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 कोड जनरेट करने के लिए, प्रोटोकॉल बफ़र कंपाइलर प्लगिन का इस्तेमाल करने का तरीका भी शामिल है.

सबसे पहले, कोडलैब की वर्किंग डायरेक्ट्री बनाएं और उसमें cd करें:

mkdir streaming-grpc-go-getting-started && cd streaming-grpc-go-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-go-streaming/start_here

इसके अलावा, सिर्फ़ कोडलैब डायरेक्ट्री वाली .zip फ़ाइल डाउनलोड करके, उसे मैन्युअल तरीके से अनज़िप किया जा सकता है.

अगर आपको लागू करने के लिए टाइप नहीं करना है, तो पूरा सोर्स कोड GitHub पर उपलब्ध है.

3. संदेशों और सेवाओं के बारे में जानकारी

सबसे पहले, प्रोटोकॉल बफ़र का इस्तेमाल करके, ऐप्लिकेशन की gRPC सेवा, उसके आरपीसी तरीके, और उसके अनुरोध और जवाब के मैसेज टाइप तय करें. आपकी सेवा से ये सुविधाएं मिलेंगी:

  • सर्वर लागू करता है और क्लाइंट कॉल करता है, जिन्हें RPC तरीके ListFeatures, RecordRoute, और RouteChat कहा जाता है.
  • मैसेज टाइप Point, Feature, Rectangle, RouteNote, और RouteSummary, जो ऊपर दिए गए तरीकों को कॉल करते समय क्लाइंट और सर्वर के बीच डेटा स्ट्रक्चर के तौर पर शेयर किए जाते हैं.

ये आरपीसी तरीके और इनके मैसेज टाइप, दिए गए सोर्स कोड की 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, Point में बताई गई जगह पर मौजूद किसी चीज़ के नाम या डाक पते के लिए string फ़ील्ड का इस्तेमाल करता है:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

इसके बाद, Rectangle मैसेज है. यह अक्षांश-देशांतर आयत को दिखाता है. इसे दो विकर्ण विपरीत बिंदुओं "lo" और "hi" के तौर पर दिखाया जाता है.

message Rectangle {
  // One corner of the rectangle.
  Point lo = 1;

  // The other corner of the rectangle.
  Point hi = 2;
}

साथ ही, RouteNote मैसेज, जो किसी समय पर भेजे गए मैसेज को दिखाता है.

message RouteNote {
  // The location from which the message is sent.
  Point location = 1;

  // The message to be sent.
  string message = 2;
}

हमें RouteSummary पर भेजा गया मैसेज भी चाहिए होगा. यह मैसेज, RecordRoute आरपीसी के जवाब में मिलता है. इसके बारे में अगले सेक्शन में बताया गया है. इसमें मिले अलग-अलग पॉइंट की संख्या, पहचानी गई सुविधाओं की संख्या, और तय की गई कुल दूरी शामिल होती है. यह दूरी, हर पॉइंट के बीच की दूरी के कुल योग के तौर पर होती है.

message RouteSummary {
  // The number of points received.
  int32 point_count = 1;

  // The number of known features passed while traversing the route.
  int32 feature_count = 2;

  // The distance covered in metres.
  int32 distance = 3;

  // The duration of the traversal in seconds.
  int32 elapsed_time = 4;
}

सेवा देने के तरीके तय करना

किसी सेवा को तय करने के लिए, अपनी .proto फ़ाइल में सेवा का नाम तय करें. route_guide.proto फ़ाइल में service स्ट्रक्चर होता है. इसका नाम RouteGuide होता है. यह ऐप्लिकेशन की सेवा के ज़रिए उपलब्ध कराए गए एक या उससे ज़्यादा तरीकों के बारे में बताता है.

अपनी सेवा की परिभाषा में RPC तरीके तय करें. साथ ही, उनके अनुरोध और जवाब के टाइप तय करें. इस कोडलैब के इस सेक्शन में, हम इन चीज़ों के बारे में जानेंगे:

ListFeatures

यह कुकी, दिए गए Rectangle में उपलब्ध Features को इकट्ठा करती है. नतीजे एक साथ नहीं दिखाए जाते, बल्कि स्ट्रीम किए जाते हैं.उदाहरण के लिए, बार-बार दिखने वाले फ़ील्ड के साथ जवाब के मैसेज में ऐसा होता है. ऐसा इसलिए, क्योंकि रेक्टैंगल में बहुत बड़ा एरिया शामिल हो सकता है और इसमें कई सुविधाएं हो सकती हैं.

इस RPC के लिए, सर्वर-साइड स्ट्रीमिंग RPC सही है: क्लाइंट, सर्वर को अनुरोध भेजता है और उसे वापस मैसेज की एक स्ट्रीम मिलती है. क्लाइंट, जवाब के तौर पर मिली स्ट्रीम से तब तक डेटा पढ़ता है, जब तक कोई और मैसेज नहीं मिलता. हमारे उदाहरण में दिखाया गया है कि रिस्पॉन्स टाइप से पहले स्ट्रीम कीवर्ड रखकर, सर्वर-साइड स्ट्रीमिंग के तरीके के बारे में बताया जाता है.

rpc ListFeatures(Rectangle) returns (stream Feature) {}

RecordRoute

यह फ़ंक्शन, तय किए गए रास्ते पर मौजूद Points की स्ट्रीम को स्वीकार करता है. साथ ही, रास्ता तय करने की प्रोसेस पूरी होने पर RouteSummary दिखाता है.

इस मामले में, क्लाइंट-साइड स्ट्रीमिंग आरपीसी सही लगती है: क्लाइंट, मैसेज का क्रम लिखता है और उन्हें सर्वर को भेजता है. इसके लिए, वह उपलब्ध स्ट्रीम का फिर से इस्तेमाल करता है. क्लाइंट के मैसेज लिखने के बाद, वह सर्वर के उन सभी मैसेज को पढ़ने और जवाब देने का इंतज़ार करता है. क्लाइंट-साइड स्ट्रीमिंग के तरीके के बारे में बताने के लिए, स्ट्रीम कीवर्ड को अनुरोध के टाइप से पहले रखा जाता है.

rpc RecordRoute(stream Point) returns (RouteSummary) {}

RouteChat

यह कुकी, रास्ते पर चलते समय भेजे गए RouteNotes की स्ट्रीम को स्वीकार करती है. साथ ही, अन्य RouteNotes (जैसे, अन्य उपयोगकर्ताओं से मिले RouteNotes) को भी स्वीकार करती है.

दोनों दिशाओं में स्ट्रीमिंग की सुविधा का इस्तेमाल, इस तरह के मामलों में किया जा सकता है. द्विदिश स्ट्रीमिंग आरपीसी में, दोनों पक्ष पढ़ने-लिखने वाली स्ट्रीम का इस्तेमाल करके मैसेज का क्रम भेजते हैं. ये दोनों स्ट्रीम अलग-अलग काम करती हैं. इसलिए, क्लाइंट और सर्वर अपनी पसंद के हिसाब से किसी भी क्रम में डेटा पढ़ और लिख सकते हैं.

उदाहरण के लिए, सर्वर अपने जवाब लिखने से पहले, सभी क्लाइंट मैसेज पाने का इंतज़ार कर सकता है. इसके अलावा, वह एक मैसेज पढ़ सकता है और फिर एक मैसेज लिख सकता है. इसके अलावा, वह पढ़ने और लिखने के किसी अन्य कॉम्बिनेशन का इस्तेमाल कर सकता है.

हर स्ट्रीम में मैसेज का क्रम बना रहता है. इस तरह के तरीके को तय करने के लिए, अनुरोध और जवाब, दोनों से पहले स्ट्रीम कीवर्ड रखा जाता है.

rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

4. क्लाइंट और सर्वर कोड जनरेट करना

इसके बाद, प्रोटोकॉल बफ़र कंपाइलर का इस्तेमाल करके, .proto फ़ाइल से क्लाइंट और सर्वर, दोनों के लिए बॉयलरप्लेट gRPC कोड जनरेट करें. 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 तरीके को कॉल करता है. साथ ही, इसमें सर्वर के लिए फ़ंक्शन होते हैं. इनका इस्तेमाल करके, सर्वर रिमोट सेवा उपलब्ध कराता है.

इसके बाद, हम सर्वर-साइड पर तरीकों को लागू करेंगे, ताकि जब क्लाइंट कोई अनुरोध भेजे, तो सर्वर जवाब दे सके.

5. सेवा लागू करना

सबसे पहले, देखते हैं कि RouteGuide सर्वर कैसे बनाया जाता है. RouteGuide सेवा को काम करने के लिए, दो चीज़ें ज़रूरी होती हैं:

  • हमारी सेवा की परिभाषा से जनरेट किए गए सेवा इंटरफ़ेस को लागू करना: हमारी सेवा का असली "काम" करना.
  • क्लाइंट से मिले अनुरोधों को सुनने और उन्हें सही सेवा लागू करने के लिए, gRPC सर्वर को चालू करना.

आइए, server/server.go में RouteGuide को लागू करते हैं.

RouteGuide को लागू करना

हमें जनरेट किए गए RouteGuideService इंटरफ़ेस को लागू करना होगा. लागू होने पर यह ऐसा दिखेगा.

type routeGuideServer struct {
        ...
}
...
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
        ...
}
...

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
        ...
}
...

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
        ...
}

आइए, हर आरपीसी को लागू करने के तरीके के बारे में विस्तार से जानते हैं.

सर्वर-साइड स्ट्रीमिंग आरपीसी

हमारी किसी एक स्ट्रीमिंग आरपीसी से शुरुआत करें. ListFeatures एक सर्वर-साइड स्ट्रीमिंग आरपीसी है. इसलिए, हमें अपने क्लाइंट को कई Feature वापस भेजने होंगे.

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
  for _, feature := range s.savedFeatures {
    if inRange(feature.Location, rect) {
      if err := stream.Send(feature); err != nil {
        return err
      }
    }
  }
  return nil
}

जैसा कि आप देख सकते हैं, इस बार हमें अपने तरीके के पैरामीटर में, सामान्य अनुरोध और जवाब वाले ऑब्जेक्ट के बजाय, एक अनुरोध ऑब्जेक्ट (Rectangle जिसमें हमारा क्लाइंट Features ढूंढना चाहता है) और जवाब लिखने के लिए एक खास RouteGuide_ListFeaturesServer ऑब्जेक्ट मिलता है. इस तरीके में, हम जितने Feature ऑब्जेक्ट लौटाने होते हैं उन्हें भरते हैं. इसके बाद, उन्हें Send() तरीके का इस्तेमाल करके RouteGuide_ListFeaturesServer में लिखते हैं. आखिर में, सामान्य आरपीसी की तरह ही हम nil गड़बड़ी का मैसेज भेजते हैं, ताकि gRPC को यह पता चल सके कि हमने जवाब लिखना पूरा कर लिया है. अगर इस कॉल में कोई गड़बड़ी होती है, तो हम एक नॉन-निल गड़बड़ी दिखाते हैं. gRPC लेयर, इसे वायर पर भेजने के लिए सही आरपीसी स्टेटस में बदल देगी.

क्लाइंट-साइड स्ट्रीमिंग आरपीसी

अब हम एक ऐसे तरीके के बारे में बात करते हैं जो थोड़ा मुश्किल है: क्लाइंट-साइड स्ट्रीमिंग का तरीका RecordRoute. इसमें हमें क्लाइंट से Points की स्ट्रीम मिलती है और हम उनकी यात्रा के बारे में जानकारी के साथ एक RouteSummary वापस भेजते हैं. जैसा कि आप देख सकते हैं, इस बार तरीके में कोई भी अनुरोध पैरामीटर नहीं है. इसके बजाय, इसे RouteGuide_RecordRouteServer स्ट्रीम मिलती है. सर्वर इसका इस्तेमाल मैसेज पढ़ने और लिखने, दोनों के लिए कर सकता है. यह Recv() तरीके का इस्तेमाल करके क्लाइंट के मैसेज पा सकता है और SendAndClose() तरीके का इस्तेमाल करके अपना एक जवाब दे सकता है.

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
  var pointCount, featureCount, distance int32
  var lastPoint *pb.Point
  startTime := time.Now()
  for {
    point, err := stream.Recv()
    if err == io.EOF {
      endTime := time.Now()
      return stream.SendAndClose(&pb.RouteSummary{
        PointCount:   pointCount,
        FeatureCount: featureCount,
        Distance:     distance,
        ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),
      })
    }
    if err != nil {
      return err
    }
    pointCount++
    for _, feature := range s.savedFeatures {
      if proto.Equal(feature.Location, point) {
        featureCount++
      }
    }
    if lastPoint != nil {
      distance += calcDistance(lastPoint, point)
    }
    lastPoint = point
  }
}

इस उदाहरण में, हम RouteGuide_RecordRouteServer के Recv() तरीके का इस्तेमाल करके, क्लाइंट के अनुरोधों को बार-बार अनुरोध ऑब्जेक्ट (इस मामले में Point) में तब तक पढ़ते हैं, जब तक कोई और मैसेज नहीं मिलता. सर्वर को हर कॉल के बाद, Recv() से मिली गड़बड़ी की जांच करनी होती है. अगर यह nil है, तो स्ट्रीम अब भी ठीक है और इसे पढ़ना जारी रखा जा सकता है. अगर यह io.EOF है, तो मैसेज स्ट्रीम खत्म हो गई है और सर्वर अपना RouteSummary वापस भेज सकता है. अगर इसमें कोई अन्य वैल्यू है, तो हम गड़बड़ी को "as is" के तौर पर दिखाते हैं, ताकि gRPC लेयर इसे आरपीसी स्टेटस में बदल सके.

दोनों दिशाओं में डेटा ट्रांसफ़र करने वाली स्ट्रीमिंग आरपीसी

आखिर में, आइए हम दोनों दिशाओं में डेटा स्ट्रीम करने वाले आरपीसी RouteChat() पर नज़र डालें.

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
  for {
    in, err := stream.Recv()
    if err == io.EOF {
      return nil
    }
    if err != nil {
      return err
    }
    key := serialize(in.Location)

    s.mu.Lock()
    s.routeNotes[key] = append(s.routeNotes[key], in)
    // Note: this copy prevents blocking other clients while serving this one.
    // We don't need to do a deep copy, because elements in the slice are
    // insert-only and never modified.
    rn := make([]*pb.RouteNote, len(s.routeNotes[key]))
    copy(rn, s.routeNotes[key])
    s.mu.Unlock()

    for _, note := range rn {
      if err := stream.Send(note); err != nil {
        return err
      }
    }
  }
}

इस बार हमें एक RouteGuide_RouteChatServer स्ट्रीम मिलती है. इसका इस्तेमाल, क्लाइंट-साइड स्ट्रीमिंग के उदाहरण की तरह ही मैसेज पढ़ने और लिखने के लिए किया जा सकता है. हालांकि, इस बार हम अपने तरीके की स्ट्रीम के ज़रिए वैल्यू तब तक वापस भेजते हैं, जब तक क्लाइंट अपनी मैसेज स्ट्रीम में मैसेज लिख रहा होता है. यहां पढ़ने और लिखने का सिंटैक्स, क्लाइंट-स्ट्रीमिंग के तरीके से काफ़ी मिलता-जुलता है. हालांकि, सर्वर SendAndClose() के बजाय स्ट्रीम के send() तरीके का इस्तेमाल करता है, क्योंकि यह कई जवाब लिख रहा है. हालांकि, दोनों पक्षों को एक-दूसरे के मैसेज उसी क्रम में मिलेंगे जिस क्रम में उन्हें लिखा गया था. हालांकि, क्लाइंट और सर्वर, दोनों ही किसी भी क्रम में पढ़ और लिख सकते हैं. स्ट्रीम पूरी तरह से अलग-अलग काम करती हैं.

सर्वर शुरू करना

सभी तरीकों को लागू करने के बाद, हमें एक gRPC सर्वर भी शुरू करना होगा, ताकि क्लाइंट हमारी सेवा का इस्तेमाल कर सकें. नीचे दिए गए स्निपेट में बताया गया है कि हम अपनी RouteGuide सेवा के लिए ऐसा कैसे करते हैं:

lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
  log.Fatalf("failed to listen: %v", err)
}

grpcServer := grpc.NewServer()

s := &routeGuideServer{routeNotes: make(map[string][]*pb.RouteNote)}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)

यहां सिलसिलेवार तरीके से बताया गया है कि main() में क्या हो रहा है:

  1. lis, err := net.Listen(...) का इस्तेमाल करके, रिमोट क्लाइंट के अनुरोधों को सुनने के लिए टीसीपी पोर्ट तय करें. डिफ़ॉल्ट रूप से, ऐप्लिकेशन टीसीपी पोर्ट 50051 का इस्तेमाल करता है. इसे वैरिएबल port में तय किया जाता है. इसके अलावा, सर्वर चलाते समय कमांड लाइन पर --port स्विच पास करके भी इसे तय किया जा सकता है. अगर टीसीपी पोर्ट नहीं खोला जा सकता, तो ऐप्लिकेशन बंद हो जाता है और एक गंभीर गड़बड़ी दिखती है.
  2. grpc.NewServer(...) का इस्तेमाल करके gRPC सर्वर का एक इंस्टेंस बनाएं. इस इंस्टेंस का नाम grpcServer रखें.
  3. routeGuideServer के लिए एक पॉइंटर बनाएं. यह ऐप्लिकेशन की एपीआई सेवा को दिखाता है. पॉइंटर का नाम s. रखें
  4. ऐरे s.savedFeatures में वैल्यू जोड़ने के लिए, s.loadFeatures() का इस्तेमाल करें.
  5. gRPC सर्वर के साथ, हमारी सेवा को लागू करने की सुविधा रजिस्टर करें.
  6. क्लाइंट के अनुरोधों को ब्लॉक करने के लिए, सर्वर पर Serve() को कॉल करें. यह तब तक जारी रहता है, जब तक प्रोसेस बंद नहीं हो जाती या Stop() को कॉल नहीं किया जाता.

loadFeatures() फ़ंक्शन, निर्देशांकों से जगह की जानकारी पाने के लिए server/testdata.go से मैपिंग करता है.

6. क्लाइंट बनाना

अब client/client.go में बदलाव करें. यहीं पर आपको क्लाइंट कोड लागू करना होगा.

रिमोट सेवा के तरीकों को कॉल करने के लिए, हमें सबसे पहले सर्वर के साथ कम्यूनिकेट करने के लिए, gRPC चैनल बनाना होगा. हम इसे सर्वर के टारगेट यूआरआई स्ट्रिंग को क्लाइंट के main() फ़ंक्शन में grpc.NewClient() के तौर पर पास करके बनाते हैं. इस मामले में, यह सिर्फ़ पता और पोर्ट नंबर है. ऐसा इस तरह किया जाता है:

// 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()

सर्वर का पता, serverAddr वैरिएबल से तय होता है. यह डिफ़ॉल्ट रूप से localhost:50051 होता है. क्लाइंट चलाते समय, कमांड लाइन पर --addr स्विच का इस्तेमाल करके इसे बदला जा सकता है.

अगर क्लाइंट को ऐसी सेवा से कनेक्ट करना है जिसके लिए पुष्टि करने वाले क्रेडेंशियल की ज़रूरत होती है, जैसे कि टीएलएस या JWT क्रेडेंशियल, तो क्लाइंट grpc.NewClient में पैरामीटर के तौर पर DialOptions ऑब्जेक्ट पास कर सकता है. इसमें ज़रूरी क्रेडेंशियल शामिल होते हैं. RouteGuide सेवा के लिए किसी क्रेडेंशियल की ज़रूरत नहीं होती.

gRPC चैनल सेट अप करने के बाद, हमें Go फ़ंक्शन कॉल के ज़रिए आरपीसी करने के लिए, क्लाइंट स्टब की ज़रूरत होती है. हमें स्टब, ऐप्लिकेशन की .proto फ़ाइल से जनरेट हुई route_guide_grpc.pb.go फ़ाइल से मिले NewRouteGuideClient तरीके का इस्तेमाल करके मिलता है.

import (pb "github.com/grpc-ecosystem/codelabs/getting_started_streaming/routeguide")

client := pb.NewRouteGuideClient(conn)

कॉल करने की सेवा के तरीके

अब देखते हैं कि हम अपनी सेवा के तरीकों को कैसे कॉल करते हैं. gRPC-Go में, आरपीसी, ब्लॉकिंग/सिंक्रोनस मोड में काम करती हैं. इसका मतलब है कि आरपीसी कॉल, सर्वर के जवाब का इंतज़ार करता है. इसके बाद, यह या तो जवाब देगा या गड़बड़ी दिखाएगा.

सर्वर-साइड स्ट्रीमिंग आरपीसी

यहां हम सर्वर-साइड स्ट्रीमिंग के तरीके ListFeatures को कॉल करते हैं. यह भौगोलिक Feature ऑब्जेक्ट की स्ट्रीम दिखाता है.

rect := &pb.Rectangle{ ... }  // initialize a pb.Rectangle
log.Printf("Looking for features within %v", rect)
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {
  log.Fatalf("client.ListFeatures failed: %v", err)
}
for {
  // For server-to-client streaming RPCs, you call stream.Recv() until it
  // returns io.EOF.
  feature, err := stream.Recv()
  if err == io.EOF {
    break
  }
  if err != nil {
    log.Fatalf("client.ListFeatures failed: %v", err)
  }
  log.Printf("Feature: name: %q, point:(%v, %v)", feature.GetName(),
    feature.GetLocation().GetLatitude(), feature.GetLocation().GetLongitude())
}

सामान्य आरपीसी की तरह, हम इस तरीके को कॉन्टेक्स्ट और अनुरोध पास करते हैं. हालांकि, हमें रिस्पॉन्स ऑब्जेक्ट के बजाय, RouteGuide_ListFeaturesClient का इंस्टेंस मिलता है. क्लाइंट, सर्वर के जवाबों को पढ़ने के लिए RouteGuide_ListFeaturesClient स्ट्रीम का इस्तेमाल कर सकता है. हम RouteGuide_ListFeaturesClient के Recv() तरीके का इस्तेमाल करके, सर्वर के जवाबों को बार-बार पढ़ते हैं. ऐसा तब तक किया जाता है, जब तक कोई और मैसेज नहीं मिलता. इस मामले में, जवाब प्रोटोकॉल बफ़र ऑब्जेक्ट (यहां Feature) का इस्तेमाल किया जाता है. क्लाइंट को हर कॉल के बाद, Recv() से मिले err की जांच करनी होती है. अगर nil है, तो स्ट्रीम अब भी ठीक है और इसे पढ़ा जा सकता है. अगर यह io.EOF है, तो मैसेज स्ट्रीम खत्म हो गई है. इसके अलावा, आरपीसी में कोई गड़बड़ी होनी चाहिए, जिसे err के ज़रिए पास किया जाता है.

क्लाइंट-साइड स्ट्रीमिंग आरपीसी

क्लाइंट-साइड स्ट्रीमिंग का तरीका RecordRoute, सर्वर-साइड के तरीके जैसा ही होता है. हालांकि, इसमें हम सिर्फ़ कॉन्टेक्स्ट पास करते हैं और हमें RouteGuide_RecordRouteClient स्ट्रीम वापस मिलती है. इसका इस्तेमाल, मैसेज लिखने और पढ़ने, दोनों के लिए किया जा सकता है.

// Create a random number of random points
r := rand.New(rand.NewSource(time.Now().UnixNano()))
pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points
var points []*pb.Point
for i := 0; i < pointCount; i++ {
  points = append(points, randomPoint(r))
}
log.Printf("Traversing %d points.", len(points))
c2sStream, err := client.RecordRoute(context.TODO())
if err != nil {
  log.Fatalf("client.RecordRoute failed: %v", err)
}
// Stream each point to the server.
for _, point := range points {
  if err := c2sStream.Send(point); err != nil {
    log.Fatalf("client.RecordRoute: stream.Send(%v) failed: %v", point, err)
  }
}
// Close the stream and receive the RouteSummary from the server.
reply, err := c2sStream.CloseAndRecv()
if err != nil {
  log.Fatalf("client.RecordRoute failed: %v", err)
}
log.Printf("Route summary: %v", reply)

RouteGuide_RecordRouteClient में एक Send() तरीका होता है. इसका इस्तेमाल करके, हम सर्वर को अनुरोध भेज सकते हैं. Send() का इस्तेमाल करके, स्ट्रीम में क्लाइंट के अनुरोध लिखने के बाद, हमें स्ट्रीम पर CloseAndRecv() को कॉल करना होगा. इससे gRPC को यह पता चलेगा कि हमने लिखना पूरा कर लिया है और हमें जवाब मिलने की उम्मीद है. हमें CloseAndRecv() से मिले err से, आरपीसी का स्टेटस मिलता है. अगर स्थिति nil है, तो CloseAndRecv() से मिलने वाली पहली वैल्यू, सर्वर से मिला मान्य रिस्पॉन्स होगी.

दोनों दिशाओं में डेटा ट्रांसफ़र करने वाली स्ट्रीमिंग आरपीसी

आखिर में, आइए हम दोनों दिशाओं में डेटा स्ट्रीम करने वाले आरपीसी RouteChat() पर नज़र डालें. RecordRoute के मामले में, हम सिर्फ़ कॉन्टेक्स्ट ऑब्जेक्ट को पास करते हैं और हमें एक स्ट्रीम मिलती है. इसका इस्तेमाल मैसेज लिखने और पढ़ने, दोनों के लिए किया जा सकता है. हालांकि, इस बार हम अपने तरीके की स्ट्रीम के ज़रिए वैल्यू वापस भेजते हैं. इस दौरान, सर्वर अब भी उनकी मैसेज स्ट्रीम में मैसेज लिख रहा होता है.

biDiStream, err := client.RouteChat(context.Background())
if err != nil {
  log.Fatalf("client.RouteChat failed: %v", err)
}
// this channel is used to wait for the receive goroutine to finish.
recvDoneCh := make(chan struct{})
// receive goroutine.
go func() {
  for {
    in, err := biDiStream.Recv()
    if err == io.EOF {
      // read done.
      close(recvDoneCh)
      return
    }
    if err != nil {
      log.Fatalf("client.RouteChat failed: %v", err)
    }
    log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)
  }
}()
// send messages simultaneously.
for _, note := range notes {
  if err := biDiStream.Send(note); err != nil {
    log.Fatalf("client.RouteChat: stream.Send(%v) failed: %v", note, err)
  }
}
biDiStream.CloseSend()
// wait for the receive goroutine to finish.
<-recvDoneCh

यहां पढ़ने और लिखने का सिंटैक्स, क्लाइंट-साइड स्ट्रीमिंग के तरीके से काफ़ी मिलता-जुलता है. हालांकि, कॉल पूरा होने के बाद हम स्ट्रीम के CloseSend() तरीके का इस्तेमाल करते हैं. हालांकि, दोनों पक्षों को एक-दूसरे के मैसेज उसी क्रम में मिलेंगे जिस क्रम में उन्हें लिखा गया था. हालांकि, क्लाइंट और सर्वर, दोनों ही किसी भी क्रम में पढ़ और लिख सकते हैं. स्ट्रीम पूरी तरह से अलग-अलग काम करती हैं.

7. इसे आज़माएं

पुष्टि करें कि सर्वर और क्लाइंट एक-दूसरे के साथ सही तरीके से काम कर रहे हैं. इसके लिए, ऐप्लिकेशन की वर्किंग डायरेक्ट्री में ये कमांड चलाएं:

  1. सर्वर को एक टर्मिनल में चलाएं:
cd server
go run .
  1. क्लाइंट को किसी दूसरे टर्मिनल से चलाएं:
cd client
go run .

आपको इस तरह का आउटपुट दिखेगा. इसमें टाइमस्टैंप को हटा दिया गया है, ताकि जानकारी साफ़ तौर पर दिख सके:

Looking for features within lo:<latitude:400000000 longitude:-750000000 > hi:<latitude:420000000 longitude:-730000000 >
name:"Patriots Path, Mendham, NJ 07945, USA" location:<latitude:407838351 longitude:-746143763 >
...
name:"3 Hasta Way, Newton, NJ 07860, USA" location:<latitude:410248224 longitude:-747127767 >
Traversing 56 points.
Route summary: point_count:56 distance:497013163
Got message First message at point(0, 1)
Got message Second message at point(0, 2)
Got message Third message at point(0, 3)
Got message First message at point(0, 1)
Got message Fourth message at point(0, 1)
Got message Second message at point(0, 2)
Got message Fifth message at point(0, 2)
Got message Third message at point(0, 3)
Got message Sixth message at point(0, 3)

8. आगे क्या करना है