gRPC-Go দিয়ে শুরু করা - স্ট্রিমিং

1. ভূমিকা

এই কোডল্যাবে, আপনি একটি ক্লায়েন্ট এবং সার্ভার তৈরি করতে gRPC-Go ব্যবহার করবেন যা Go-তে লেখা একটি রুট-ম্যাপিং অ্যাপ্লিকেশনের ভিত্তি তৈরি করে।

টিউটোরিয়ালের শেষে, আপনার কাছে একটি ক্লায়েন্ট থাকবে যেটি একটি দূরবর্তী সার্ভারের সাথে সংযোগ করে একটি ক্লায়েন্টের রুটের বৈশিষ্ট্য সম্পর্কে তথ্য পেতে, একটি ক্লায়েন্টের রুটের একটি সারাংশ তৈরি করতে এবং সার্ভার এবং অন্যান্য ক্লায়েন্টদের সাথে ট্রাফিক আপডেটের মতো রুটের তথ্য বিনিময় করতে।

পরিষেবাটি একটি প্রোটোকল বাফার ফাইলে সংজ্ঞায়িত করা হয়েছে, যা ক্লায়েন্ট এবং সার্ভারের জন্য বয়লারপ্লেট কোড তৈরি করতে ব্যবহার করা হবে যাতে তারা একে অপরের সাথে যোগাযোগ করতে পারে, সেই কার্যকারিতা বাস্তবায়নে আপনার সময় এবং প্রচেষ্টা বাঁচাতে পারে।

এই তৈরি করা কোডটি সার্ভার এবং ক্লায়েন্টের মধ্যে যোগাযোগের জটিলতাই নয়, ডেটা সিরিয়ালাইজেশন এবং ডিসিরিয়ালাইজেশনেরও যত্ন নেয়।

আপনি কি শিখবেন

  • একটি পরিষেবা API সংজ্ঞায়িত করতে প্রোটোকল বাফারগুলি কীভাবে ব্যবহার করবেন।
  • স্বয়ংক্রিয় কোড জেনারেশন ব্যবহার করে প্রোটোকল বাফার সংজ্ঞা থেকে কীভাবে একটি জিআরপিসি-ভিত্তিক ক্লায়েন্ট এবং সার্ভার তৈরি করবেন।
  • gRPC-এর সাথে ক্লায়েন্ট-সার্ভার স্ট্রিমিং যোগাযোগের একটি বোঝাপড়া।

এই কোডল্যাবটি GRPC-তে নতুন Go ডেভেলপারদের জন্য বা gRPC-এর রিফ্রেশার খোঁজার জন্য বা বিতরণ করা সিস্টেম তৈরি করতে আগ্রহী অন্যদের লক্ষ্য করে। কোন পূর্ব gRPC অভিজ্ঞতা প্রয়োজন নেই.

2. আপনি শুরু করার আগে

পূর্বশর্ত

নিশ্চিত করুন যে আপনি নিম্নলিখিত ইনস্টল করেছেন:

  • Go টুলচেন সংস্করণ 1.24.5 বা তার পরে। ইনস্টলেশন নির্দেশাবলীর জন্য, Go's Getting start দেখুন।
  • প্রোটোকল বাফার কম্পাইলার, 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"

কোড পান

যাতে আপনাকে সম্পূর্ণরূপে স্ক্র্যাচ থেকে শুরু করতে না হয়, এই কোডল্যাবটি আপনাকে সম্পূর্ণ করার জন্য অ্যাপ্লিকেশনের সোর্স কোডের একটি স্ক্যাফোল্ড প্রদান করে। বয়লারপ্লেট জিআরপিসি কোড জেনারেট করতে প্রোটোকল বাফার কম্পাইলার প্লাগইনগুলি ব্যবহার করা সহ, নিম্নলিখিত পদক্ষেপগুলি আপনাকে কীভাবে অ্যাপ্লিকেশনটি শেষ করতে হবে তা দেখাবে।

প্রথমে, কোডল্যাব ওয়ার্কিং ডিরেক্টরি তৈরি করুন এবং এটিতে 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 পদ্ধতি এবং এর অনুরোধ এবং প্রতিক্রিয়া বার্তার ধরনগুলি সংজ্ঞায়িত করা। আপনার পরিষেবা প্রদান করবে:

  • RPC পদ্ধতিগুলিকে ListFeatures , RecordRoute , এবং RouteChat বলা হয় যা সার্ভার প্রয়োগ করে এবং ক্লায়েন্ট কল করে।
  • বার্তার ধরন হল Point , Feature , Rectangle , RouteNote এবং RouteSummary , যা উপরের পদ্ধতিগুলি কল করার সময় ক্লায়েন্ট এবং সার্ভারের মধ্যে আদান-প্রদান করা হয়।

এই RPC পদ্ধতি এবং এর বার্তার ধরনগুলি প্রদত্ত সোর্স কোডের routeguide/route_guide.proto ফাইলে সংজ্ঞায়িত করা হবে।

প্রোটোকল বাফারগুলি সাধারণত প্রোটোবাফ হিসাবে পরিচিত। 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 RPC এর প্রতিক্রিয়া হিসাবে গৃহীত হয়েছে যা পরবর্তী বিভাগে ব্যাখ্যা করা হয়েছে। এতে প্রাপ্ত পৃথক পয়েন্টের সংখ্যা, সনাক্ত করা বৈশিষ্ট্যের সংখ্যা এবং প্রতিটি বিন্দুর মধ্যে দূরত্বের ক্রমবর্ধমান সমষ্টি হিসাবে আচ্ছাদিত মোট দূরত্ব রয়েছে।

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 ফাইলটিতে RouteGuide নামে একটি service কাঠামো রয়েছে যা অ্যাপ্লিকেশনটির পরিষেবা দ্বারা প্রদত্ত এক বা একাধিক পদ্ধতিকে সংজ্ঞায়িত করে৷

আপনার পরিষেবার সংজ্ঞার মধ্যে RPC পদ্ধতিগুলি সংজ্ঞায়িত করুন, তাদের অনুরোধ এবং প্রতিক্রিয়ার ধরনগুলি নির্দিষ্ট করুন৷ কোডল্যাবের এই বিভাগে, আসুন সংজ্ঞায়িত করা যাক:

তালিকা বৈশিষ্ট্য

প্রদত্ত Rectangle মধ্যে উপলব্ধ Feature প্রাপ্ত করে৷ ফলাফলগুলি একবারে প্রত্যাবর্তনের পরিবর্তে স্ট্রিম করা হয় (যেমন একটি পুনরাবৃত্তি ক্ষেত্র সহ একটি প্রতিক্রিয়া বার্তায়), কারণ আয়তক্ষেত্রটি একটি বৃহৎ এলাকা কভার করতে পারে এবং এতে বিপুল সংখ্যক বৈশিষ্ট্য থাকতে পারে।

এই RPC-এর জন্য একটি উপযুক্ত প্রকার হল সার্ভার-সাইড স্ট্রিমিং RPC: ক্লায়েন্ট সার্ভারে একটি অনুরোধ পাঠায় এবং বার্তাগুলির একটি ক্রম পড়ার জন্য একটি স্ট্রিম পায়। ক্লায়েন্ট প্রত্যাবর্তিত স্ট্রীম থেকে পাঠ করে যতক্ষণ না আর কোনো বার্তা নেই। আপনি যেমন আমাদের উদাহরণে দেখতে পাচ্ছেন, আপনি প্রতিক্রিয়া প্রকারের আগে স্ট্রিম কীওয়ার্ড স্থাপন করে একটি সার্ভার-সাইড স্ট্রিমিং পদ্ধতি নির্দিষ্ট করেন।

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

রেকর্ডরুট

ট্র্যাভার্সাল করা রুটে Point s-এর একটি স্ট্রীম গ্রহণ করে, ট্র্যাভার্সাল সম্পূর্ণ হলে একটি RouteSummary ফেরত দেয়।

একটি ক্লায়েন্ট-সাইড স্ট্রিমিং RPC এই ক্ষেত্রে উপযুক্ত বলে মনে হয়: ক্লায়েন্ট বার্তাগুলির একটি ক্রম লিখে এবং সার্ভারে পাঠায়, আবার একটি প্রদত্ত স্ট্রিম ব্যবহার করে। একবার ক্লায়েন্ট বার্তাগুলি লেখা শেষ করলে, এটি সার্ভারের জন্য সেগুলি পড়ার জন্য এবং তার প্রতিক্রিয়া ফেরত দেওয়ার জন্য অপেক্ষা করে। আপনি অনুরোধের প্রকারের আগে স্ট্রিম কীওয়ার্ড স্থাপন করে একটি ক্লায়েন্ট-সাইড স্ট্রিমিং পদ্ধতি নির্দিষ্ট করুন।

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

রুটচ্যাট

একটি রুট অতিক্রম করার সময় অন্যান্য RouteNote গুলি গ্রহণ করার সময় (যেমন অন্যান্য ব্যবহারকারীদের কাছ থেকে) প্রেরিত RouteNote এর একটি স্ট্রীম গ্রহণ করে৷

দ্বিমুখী স্ট্রিমিংয়ের ক্ষেত্রে এটি ঠিক একই ধরনের ব্যবহারের ক্ষেত্রে। একটি দ্বিমুখী স্ট্রিমিং RPC-তে উভয় পক্ষই একটি পাঠ-লেখা স্ট্রিম ব্যবহার করে বার্তাগুলির একটি ক্রম প্রেরণ করে। দুটি স্ট্রীম স্বাধীনভাবে কাজ করে, তাই ক্লায়েন্ট এবং সার্ভাররা তাদের পছন্দ অনুযায়ী পড়তে এবং লিখতে পারে।

উদাহরণস্বরূপ, সার্ভার তার প্রতিক্রিয়া লেখার আগে সমস্ত ক্লায়েন্ট বার্তা পাওয়ার জন্য অপেক্ষা করতে পারে, বা এটি বিকল্পভাবে একটি বার্তা পড়তে পারে তারপর একটি বার্তা লিখতে পারে, বা পঠিত এবং লেখার অন্য কিছু সংমিশ্রণ করতে পারে।

প্রতিটি প্রবাহে বার্তার ক্রম সংরক্ষিত হয়। আপনি অনুরোধ এবং প্রতিক্রিয়া উভয়ের আগে স্ট্রিম কীওয়ার্ড স্থাপন করে এই ধরনের পদ্ধতি নির্দিষ্ট করুন।

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 {
        ...
}

আসুন আমরা প্রতিটি RPC বাস্তবায়ন বিস্তারিতভাবে দেখি।

সার্ভার-সাইড স্ট্রিমিং RPC

আমাদের একটি স্ট্রিমিং RPC দিয়ে শুরু করুন। ListFeatures হল একটি সার্ভার-সাইড স্ট্রিমিং RPC, তাই আমাদের ক্লায়েন্টকে একাধিক 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 এ লিখি। অবশেষে, আমাদের সাধারণ RPC তে, আমরা একটি nil ত্রুটি ফেরত দিয়ে জিআরপিসিকে বলতে পারি যে আমরা প্রতিক্রিয়া লেখা শেষ করেছি। এই কলে কোনো ত্রুটি ঘটলে, আমরা একটি অ-শূন্য ত্রুটি ফেরত দিই; gRPC স্তর এটিকে তারে পাঠানোর জন্য একটি উপযুক্ত RPC স্থিতিতে অনুবাদ করবে।

ক্লায়েন্ট-সাইড স্ট্রিমিং RPC

এখন আসুন একটু জটিল কিছু দেখি: ক্লায়েন্ট-সাইড স্ট্রিমিং পদ্ধতি 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 ফেরত দিতে পারে। যদি এটির অন্য কোন মান থাকে, তাহলে আমরা "যেমন আছে" ত্রুটিটি ফেরত দিই যাতে এটি জিআরপিসি স্তর দ্বারা একটি RPC স্থিতিতে অনুবাদ করা হবে।

দ্বিমুখী স্ট্রিমিং RPC

অবশেষে, আসুন আমাদের দ্বিমুখী স্ট্রিমিং RPC 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(...) ব্যবহার করে দূরবর্তী ক্লায়েন্টের অনুরোধ শোনার জন্য ব্যবহার করার জন্য TCP পোর্ট নির্দিষ্ট করুন। ডিফল্টরূপে, অ্যাপ্লিকেশন টিসিপি পোর্ট 50051 ব্যবহার করে যা পরিবর্তনশীল port দ্বারা নির্দিষ্ট করা হয়েছে বা সার্ভার চালানোর সময় কমান্ড লাইনে --port সুইচ পাস করে। যদি TCP পোর্ট খোলা না যায়, তাহলে অ্যাপ্লিকেশনটি একটি মারাত্মক ত্রুটির সাথে শেষ হয়।
  2. grpc.NewServer(...) ব্যবহার করে gRPC সার্ভারের একটি উদাহরণ তৈরি করুন, এই উদাহরণটিকে grpcServer নামকরণ করুন।
  3. routeGuideServer এ একটি পয়েন্টার তৈরি করুন, একটি কাঠামো যা অ্যাপ্লিকেশনের API পরিষেবার প্রতিনিধিত্ব করে, পয়েন্টার s.
  4. অ্যারে s.savedFeatures পপুলেট করতে s.loadFeatures() ব্যবহার করুন।
  5. gRPC সার্ভারের সাথে আমাদের পরিষেবা বাস্তবায়ন নিবন্ধন করুন।
  6. ক্লায়েন্ট অনুরোধের জন্য একটি ব্লকিং অপেক্ষা করতে আমাদের পোর্টের বিবরণ সহ সার্ভারে কল করুন Serve() ; প্রক্রিয়াটি মারা না যাওয়া পর্যন্ত বা Stop() বলা না হওয়া পর্যন্ত এটি চলতে থাকে।

ফাংশন loadFeatures() server/testdata.go থেকে এর স্থানাঙ্ক-টু-অবস্থান ম্যাপিং পায়।

6. ক্লায়েন্ট তৈরি করুন

এখন client/client.go সম্পাদনা করুন, যেখানে আপনি ক্লায়েন্ট কোড প্রয়োগ করবেন।

দূরবর্তী পরিষেবার পদ্ধতিগুলিকে কল করার জন্য, আমাদের প্রথমে সার্ভারের সাথে যোগাযোগের জন্য একটি gRPC চ্যানেল তৈরি করতে হবে। আমরা সার্ভারের টার্গেট URI স্ট্রিং (যা এই ক্ষেত্রে সহজভাবে ঠিকানা এবং পোর্ট নম্বর) ক্লায়েন্টের 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 সুইচ দ্বারা ওভাররাইড করা যেতে পারে।

যদি ক্লায়েন্টকে এমন একটি পরিষেবার সাথে সংযোগ করতে হয় যার জন্য TLS বা JWT শংসাপত্রের মতো প্রমাণীকরণের শংসাপত্রের প্রয়োজন হয়, তাহলে ক্লায়েন্ট একটি DialOptions অবজেক্টকে একটি প্যারামিটার হিসাবে grpc.NewClient এ পাঠাতে পারে যাতে প্রয়োজনীয় শংসাপত্র রয়েছে। RouteGuide পরিষেবার কোনো শংসাপত্রের প্রয়োজন নেই।

একবার gRPC চ্যানেল সেট আপ হয়ে গেলে, Go ফাংশন কলের মাধ্যমে RPCগুলি সম্পাদন করার জন্য আমাদের একটি ক্লায়েন্ট স্টাব প্রয়োজন। অ্যাপ্লিকেশনের .proto ফাইল থেকে জেনারেট করা route_guide_grpc.pb.go ফাইল দ্বারা প্রদত্ত NewRouteGuideClient পদ্ধতি ব্যবহার করে আমরা সেই স্টাবটি পাই।

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

client := pb.NewRouteGuideClient(conn)

কল পরিষেবা পদ্ধতি

এখন আসুন আমরা আমাদের পরিষেবা পদ্ধতিগুলিকে কীভাবে বলি তা দেখি। gRPC-Go-তে, RPCগুলি একটি ব্লকিং/সিঙ্ক্রোনাস মোডে কাজ করে, যার অর্থ হল RPC কলটি সার্ভারের সাড়া দেওয়ার জন্য অপেক্ষা করে এবং হয় একটি প্রতিক্রিয়া বা ত্রুটি ফিরিয়ে দেবে।

সার্ভার-সাইড স্ট্রিমিং RPC

এখানে আমরা সার্ভার-সাইড স্ট্রিমিং পদ্ধতিকে বলি 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())
}

সাধারণ RPC এর মতো, আমরা পদ্ধতিটিকে একটি প্রসঙ্গ এবং একটি অনুরোধ পাস করি। যাইহোক, একটি প্রতিক্রিয়া বস্তু ফিরে পাওয়ার পরিবর্তে, আমরা RouteGuide_ListFeaturesClient এর একটি উদাহরণ ফিরে পাই। ক্লায়েন্ট সার্ভারের প্রতিক্রিয়া পড়তে RouteGuide_ListFeaturesClient স্ট্রীম ব্যবহার করতে পারে। আমরা RouteGuide_ListFeaturesClient এর Recv() পদ্ধতি ব্যবহার করি সার্ভারের প্রতিক্রিয়া প্রোটোকল বাফার অবজেক্টে বারবার পড়ার জন্য (এই ক্ষেত্রে একটি Feature ) যতক্ষণ না আর কোনও বার্তা না আসে: ক্লায়েন্টকে প্রতিটি কলের পরে Recv() থেকে ফিরে আসা ত্রুটিটি পরীক্ষা করতে হবে। যদি nil , স্ট্রীম এখনও ভাল এবং এটি পড়া চালিয়ে যেতে পারে; যদি এটি io.EOF হয় তাহলে বার্তা স্ট্রীম শেষ হয়েছে; অন্যথায় একটি RPC ত্রুটি থাকতে হবে, যা err এর মাধ্যমে পাস করা হয়।

ক্লায়েন্ট-সাইড স্ট্রিমিং RPC

ক্লায়েন্ট-সাইড স্ট্রিমিং পদ্ধতি 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() থেকে প্রত্যাবর্তিত ত্রুটি থেকে আমরা আমাদের RPC স্থিতি পাই। যদি স্থিতিটি nil হয়, তাহলে CloseAndRecv() থেকে প্রথম রিটার্ন মানটি একটি বৈধ সার্ভার প্রতিক্রিয়া হবে।

দ্বিমুখী স্ট্রিমিং RPC

অবশেষে, আসুন আমাদের দ্বিমুখী স্ট্রিমিং RPC 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. পরবর্তী কি