1. ভূমিকা
এই কোডল্যাবে, আপনি gRPC-Go ব্যবহার করে একটি ক্লায়েন্ট ও সার্ভার তৈরি করবেন, যা Go-তে লেখা একটি রাউট-ম্যাপিং অ্যাপ্লিকেশনের ভিত্তি তৈরি করবে।
এই টিউটোরিয়ালটি শেষ করার পর, আপনি এমন একটি ক্লায়েন্ট তৈরি করতে পারবেন যা gRPC ব্যবহার করে একটি রিমোট সার্ভারের সাথে সংযোগ স্থাপন করে ক্লায়েন্টের রুটের ফিচারগুলো সম্পর্কে তথ্য সংগ্রহ করতে, ক্লায়েন্টের রুটের একটি সারাংশ তৈরি করতে এবং সার্ভার ও অন্যান্য ক্লায়েন্টদের সাথে ট্র্যাফিক আপডেটের মতো রুটের তথ্য আদান-প্রদান করতে পারবে।
সার্ভিসটি একটি প্রোটোকল বাফারস ফাইলে সংজ্ঞায়িত করা আছে, যা ক্লায়েন্ট এবং সার্ভারের জন্য বয়লারপ্লেট কোড তৈরি করতে ব্যবহৃত হবে, যাতে তারা একে অপরের সাথে যোগাযোগ করতে পারে। এর ফলে ঐ কার্যকারিতাটি বাস্তবায়নে আপনার সময় ও শ্রম বাঁচবে।
এই জেনারেট করা কোডটি শুধু সার্ভার ও ক্লায়েন্টের মধ্যকার যোগাযোগের জটিলতাই নয়, ডেটার সিরিয়ালাইজেশন এবং ডিসিরিয়ালাইজেশনও সামলে নেয়।
আপনি যা শিখবেন
- সার্ভিস এপিআই সংজ্ঞায়িত করতে প্রোটোকল বাফার কীভাবে ব্যবহার করবেন
- স্বয়ংক্রিয় কোড জেনারেশন ব্যবহার করে প্রোটোকল বাফারস ডেফিনিশন থেকে কীভাবে একটি gRPC-ভিত্তিক ক্লায়েন্ট এবং সার্ভার তৈরি করা যায়।
- gRPC ব্যবহার করে ক্লায়েন্ট-সার্ভার স্ট্রিমিং যোগাযোগ সম্পর্কে ধারণা।
এই কোডল্যাবটি সেইসব Go ডেভেলপারদের জন্য তৈরি করা হয়েছে যারা gRPC-তে নতুন অথবা এর বিষয়ে নিজেদের জ্ঞান ঝালিয়ে নিতে চান, কিংবা যারা ডিস্ট্রিবিউটেড সিস্টেম তৈরিতে আগ্রহী। gRPC-তে পূর্ব অভিজ্ঞতার কোনো প্রয়োজন নেই।
২. শুরু করার আগে
পূর্বশর্ত
নিশ্চিত করুন যে আপনি নিম্নলিখিতগুলি ইনস্টল করেছেন:
- গো টুলচেইন সংস্করণ ১.২৪.৫ বা তার পরবর্তী সংস্করণ। ইনস্টলেশন নির্দেশাবলীর জন্য, গো-এর 'গেটিং স্টার্টেড' দেখুন।
- প্রোটোকল বাফার কম্পাইলার,
protoc, সংস্করণ ৩.২৭.১ বা তার পরবর্তী সংস্করণ। ইনস্টলেশন নির্দেশাবলীর জন্য, কম্পাইলারের ইনস্টলেশন গাইড দেখুন। - 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 ফাইলটি ডাউনলোড করে ম্যানুয়ালি আনজিপ করতে পারেন।
আপনি যদি ইমপ্লিমেন্টেশন টাইপ করা এড়াতে চান, তাহলে সম্পূর্ণ সোর্স কোডটি গিটহাবে পাওয়া যাবে ।
৩. বার্তা এবং পরিষেবাগুলি সংজ্ঞায়িত করুন
আপনার প্রথম পদক্ষেপ হলো প্রোটোকল বাফার ব্যবহার করে অ্যাপ্লিকেশনটির gRPC পরিষেবা, এর RPC পদ্ধতিসমূহ এবং এর অনুরোধ ও প্রতিক্রিয়া বার্তার ধরণগুলো সংজ্ঞায়িত করা। আপনার পরিষেবাটি প্রদান করবে:
-
ListFeatures,RecordRoute, এবংRouteChatনামক RPC মেথডগুলো, যেগুলো সার্ভার ইমপ্লিমেন্ট করে এবং ক্লায়েন্ট কল করে। -
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: ক্লায়েন্ট সার্ভারে একটি অনুরোধ পাঠায় এবং ক্রমানুসারে বার্তা পড়ার জন্য একটি স্ট্রিম ফেরত পায়। ক্লায়েন্ট ফেরত আসা স্ট্রিমটি থেকে ততক্ষণ পর্যন্ত পড়তে থাকে যতক্ষণ না আর কোনো বার্তা অবশিষ্ট থাকে। আমাদের উদাহরণে যেমনটি দেখতে পাচ্ছেন, রেসপন্স টাইপের আগে 'stream' কীওয়ার্ডটি বসিয়ে একটি সার্ভার-সাইড স্ট্রিমিং পদ্ধতি নির্দিষ্ট করা হয়।
rpc ListFeatures(Rectangle) returns (stream Feature) {}
রেকর্ডরুট
অতিক্রম করা হচ্ছে এমন কোনো রুটের উপর থাকা Point একটি ধারা গ্রহণ করে এবং অতিক্রমণ সম্পন্ন হলে একটি RouteSummary ফেরত দেয়।
এই ক্ষেত্রে একটি ক্লায়েন্ট-সাইড স্ট্রিমিং RPC উপযুক্ত বলে মনে হয়: ক্লায়েন্ট একটি প্রদত্ত স্ট্রিম ব্যবহার করে ধারাবাহিকভাবে বার্তা লেখে এবং সার্ভারে পাঠায়। বার্তা লেখা শেষ হয়ে গেলে, ক্লায়েন্ট সেগুলোর সব পড়ে প্রতিক্রিয়া ফেরত পাঠানোর জন্য সার্ভারের অপেক্ষা করে। রিকোয়েস্ট টাইপের আগে 'stream' কীওয়ার্ডটি বসিয়ে একটি ক্লায়েন্ট-সাইড স্ট্রিমিং পদ্ধতি নির্দিষ্ট করা হয়।
rpc RecordRoute(stream Point) returns (RouteSummary) {}
রুটচ্যাট
একটি রুট অতিক্রম করার সময় পাঠানো RouteNote এর একটি ধারা গ্রহণ করে, এবং একই সাথে অন্যান্য RouteNote ও (যেমন অন্য ব্যবহারকারীদের কাছ থেকে) গ্রহণ করে।
দ্বিমুখী স্ট্রিমিং-এর জন্য এটি একদম সঠিক একটি ব্যবহার। একটি দ্বিমুখী স্ট্রিমিং RPC-তে উভয় পক্ষই একটি রিড-রাইট স্ট্রিম ব্যবহার করে ধারাবাহিকভাবে বার্তা পাঠায়। এই দুটি স্ট্রিম স্বাধীনভাবে কাজ করে, ফলে ক্লায়েন্ট এবং সার্ভার তাদের পছন্দমতো যেকোনো ক্রমে পড়তে ও লিখতে পারে।
উদাহরণস্বরূপ, সার্ভার তার প্রতিক্রিয়া লেখার আগে ক্লায়েন্টের সমস্ত বার্তা পাওয়ার জন্য অপেক্ষা করতে পারে, অথবা এটি পর্যায়ক্রমে একটি বার্তা পড়ে তারপর একটি বার্তা লিখতে পারে, কিংবা পড়া এবং লেখার অন্য কোনো সংমিশ্রণ ব্যবহার করতে পারে।
প্রতিটি স্ট্রিমে মেসেজগুলোর ক্রম সংরক্ষিত থাকে। রিকোয়েস্ট এবং রেসপন্স উভয়ের আগে 'stream' কীওয়ার্ডটি বসিয়ে এই ধরনের পদ্ধতি নির্দিষ্ট করা হয়।
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
৪. ক্লায়েন্ট এবং সার্ভার কোড তৈরি করুন
এরপরে, প্রোটোকল বাফার কম্পাইলার ব্যবহার করে .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 মেথড কল করার জন্য ব্যবহার করে, এবং এমন সব ফাংশনও রয়েছে যা সার্ভার সেই রিমোট সার্ভিসটি প্রদান করার জন্য ব্যবহার করে।
এরপরে, আমরা সার্ভার-সাইডে মেথডগুলো ইমপ্লিমেন্ট করব, যাতে ক্লায়েন্ট কোনো রিকোয়েস্ট পাঠালে সার্ভার তার উত্তর দিতে পারে।
৫. পরিষেবাটি বাস্তবায়ন করুন
প্রথমে দেখা যাক আমরা কীভাবে একটি RouteGuide সার্ভার তৈরি করি। আমাদের RouteGuide সার্ভিসকে তার কাজ করানোর জন্য দুটি অংশ রয়েছে:
- আমাদের সার্ভিস ডেফিনিশন থেকে তৈরি সার্ভিস ইন্টারফেসটি ইমপ্লিমেন্ট করা: অর্থাৎ আমাদের সার্ভিসের আসল 'কাজ'টি করা।
- ক্লায়েন্টদের কাছ থেকে অনুরোধ শোনার জন্য এবং সেগুলোকে সঠিক পরিষেবা বাস্তবায়নে পাঠানোর জন্য একটি gRPC সার্ভার চালানো হচ্ছে।
চলুন server/server.go তে 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 অবজেক্ট রিটার্ন করার প্রয়োজন, ততগুলো পূরণ করি এবং RouteGuide_ListFeaturesServer এর Send() মেথড ব্যবহার করে সেগুলোকে সেখানে লিখি। সবশেষে, আমাদের সাধারণ RPC-এর মতোই, আমরা একটি nil এরর রিটার্ন করি gRPC-কে এটা জানানোর জন্য যে আমাদের রেসপন্স লেখা শেষ হয়েছে। এই কলে কোনো এরর ঘটলে, আমরা একটি non-nil এরর রিটার্ন করি; gRPC লেয়ার সেটিকে একটি উপযুক্ত 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 ফেরত দিতে পারে। যদি এর অন্য কোনো মান থাকে, আমরা এররটি যেমন আছে তেমনই ফেরত দিই, যাতে gRPC লেয়ার এটিকে একটি 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() ফাংশনে যা ঘটছে, তা ধাপে ধাপে নিচে দেওয়া হলো:
- রিমোট ক্লায়েন্টের অনুরোধ শোনার জন্য ব্যবহৃত TCP পোর্টটি
lis, err := net.Listen(...)ব্যবহার করে নির্দিষ্ট করুন। ডিফল্টরূপে, অ্যাপ্লিকেশনটি TCP পোর্ট50051ব্যবহার করে, যাportভেরিয়েবলের মাধ্যমে অথবা সার্ভার চালানোর সময় কমান্ড লাইনে--portসুইচটি পাস করে নির্দিষ্ট করা হয়। যদি TCP পোর্টটি খোলা না যায়, তাহলে অ্যাপ্লিকেশনটি একটি মারাত্মক ত্রুটির (fatal error) মাধ্যমে বন্ধ হয়ে যায়। -
grpc.NewServer(...)ব্যবহার করে gRPC সার্ভারের একটি ইনস্ট্যান্স তৈরি করুন এবং এই ইনস্ট্যান্সটির নাম দিনgrpcServer। - অ্যাপ্লিকেশনটির এপিআই পরিষেবা প্রতিনিধিত্বকারী স্ট্রাকচার `
routeGuideServerএর একটি পয়েন্টার তৈরি করুন এবং পয়েন্টারটির নামs. -
s.savedFeaturesঅ্যারেটি পূরণ করতেs.loadFeatures()ব্যবহার করুন। - আমাদের পরিষেবা বাস্তবায়নটি gRPC সার্ভারে নিবন্ধন করুন।
- ক্লায়েন্টের অনুরোধের জন্য ব্লকিং অপেক্ষা করতে আমাদের পোর্ট বিবরণ সহ সার্ভারে
Serve()কল করুন; এটি ততক্ষণ চলতে থাকে যতক্ষণ না প্রসেসটি কিল করা হয় বাStop()কল করা হয়।
loadFeatures() ফাংশনটি তার স্থানাঙ্ক-থেকে-অবস্থান ম্যাপিং server/testdata.go থেকে গ্রহণ করে।
৬. ক্লায়েন্ট তৈরি করুন
এখন client/client.go সম্পাদনা করুন, যেখানে আপনি ক্লায়েন্ট কোড লিখবেন।
রিমোট সার্ভিসের মেথডগুলো কল করার জন্য, আমাদের প্রথমে সার্ভারের সাথে যোগাযোগের জন্য একটি gRPC চ্যানেল তৈরি করতে হবে। ক্লায়েন্টের main() ফাংশনে grpc.NewClient() ফাংশনে সার্ভারের টার্গেট URI স্ট্রিং (যা এই ক্ষেত্রে কেবল অ্যাড্রেস এবং পোর্ট নম্বর) পাস করার মাধ্যমে আমরা এটি main() করি, যেমনটা নিচে দেখানো হলো:
// 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 ক্রেডেনশিয়ালের মতো অথেনটিকেশন ক্রেডেনশিয়াল প্রয়োজন, তবে ক্লায়েন্ট grpc.NewClient এ প্যারামিটার হিসেবে একটি DialOptions অবজেক্ট পাস করতে পারে, যেটিতে প্রয়োজনীয় ক্রেডেনশিয়ালগুলো থাকে। 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() থেকে ফেরত আসা এরর err চেক করতে হবে। যদি nil , তাহলে স্ট্রিমটি এখনও ঠিক আছে এবং এটি পড়া চালিয়ে যেতে পারে; যদি এটি io.EOF হয়, তাহলে মেসেজ স্ট্রিমটি শেষ হয়ে গেছে; অন্যথায় অবশ্যই একটি RPC এরর হয়েছে, যা 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() ব্যবহার করে স্ট্রিমে আমাদের ক্লায়েন্টের রিকোয়েস্টগুলো লেখা শেষ হয়ে গেলে, gRPC-কে জানানোর জন্য যে আমাদের লেখা শেষ হয়েছে এবং আমরা একটি রেসপন্স পাওয়ার অপেক্ষায় আছি, স্ট্রিমে CloseAndRecv() () কল করতে হবে। CloseAndRecv() থেকে রিটার্ন করা err থেকে আমরা আমাদের RPC স্ট্যাটাস পেয়ে থাকি। যদি স্ট্যাটাস nil হয়, তাহলে CloseAndRecv() থেকে প্রথম রিটার্ন করা ভ্যালুটি একটি বৈধ সার্ভার রেসপন্স হবে।
দ্বিমুখী স্ট্রিমিং আরপিসি
অবশেষে, চলুন আমাদের দ্বিমুখী স্ট্রিমিং 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() মেথডটি ব্যবহার করি। যদিও উভয় পক্ষই একে অপরের মেসেজগুলো লেখার ক্রমানুসারে পাবে, ক্লায়েন্ট এবং সার্ভার উভয়ই যেকোনো ক্রমে পড়তে ও লিখতে পারে — স্ট্রিমগুলো সম্পূর্ণ স্বাধীনভাবে কাজ করে।
৭. এটি পরীক্ষা করে দেখুন
অ্যাপ্লিকেশনটির ওয়ার্কিং ডিরেক্টরিতে নিম্নলিখিত কমান্ডগুলো চালিয়ে সার্ভার এবং ক্লায়েন্ট একে অপরের সাথে সঠিকভাবে কাজ করছে কিনা তা নিশ্চিত করুন:
- একটি টার্মিনালে সার্ভারটি চালান:
cd server go run .
- অন্য একটি টার্মিনাল থেকে ক্লায়েন্টটি চালান:
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)
৮. এরপর কী?
- gRPC- এর পরিচিতি এবং মূল ধারণাগুলো থেকে জানুন gRPC কীভাবে কাজ করে।
- বেসিক টিউটোরিয়ালটি সম্পূর্ণ করুন।
- এপিআই রেফারেন্সটি অন্বেষণ করুন।