1. Giriş
Bu codelab'de, Go ile yazılmış bir rota eşleme uygulamasının temelini oluşturan bir istemci ve sunucu oluşturmak için gRPC-Go'yu kullanacaksınız.
Eğitimin sonunda, bir müşterinin rotasındaki özellikler hakkında bilgi almak, müşterinin rotasının özetini oluşturmak ve trafik güncellemeleri gibi rota bilgilerini sunucu ve diğer müşterilerle paylaşmak için gRPC kullanarak uzak bir sunucuya bağlanan bir istemciniz olacak.
Hizmet, istemci ve sunucu için standart kod oluşturmak üzere kullanılacak bir Protocol Buffers dosyasında tanımlanır. Böylece, bu işlevselliği uygularken zamandan ve emekten tasarruf edersiniz.
Oluşturulan bu kod, yalnızca sunucu ile istemci arasındaki iletişimin karmaşıklıklarını değil, aynı zamanda veri serileştirme ve seri durumdan çıkarma işlemlerini de ele alır.
Neler öğreneceksiniz?
- Hizmet API'sini tanımlamak için Protocol Buffers'ı kullanma.
- Otomatik kod oluşturma kullanarak bir Protokol Arabellek tanımından gRPC tabanlı istemci ve sunucu oluşturma.
- gRPC ile istemci-sunucu akışı iletişimi hakkında bilgi sahibi olmanız gerekir.
Bu codelab, gRPC'ye yeni başlayan veya gRPC hakkında bilgi tazelemek isteyen Go geliştiricilerinin yanı sıra dağıtılmış sistemler oluşturmakla ilgilenen herkes için hazırlanmıştır. Daha önce gRPC deneyimi gerekmez.
2. Başlamadan önce
Ön koşullar
Aşağıdakileri yüklediğinizden emin olun:
- Go toolchain sürümü 1.24.5 veya daha yeni olmalıdır. Yükleme talimatları için Go'nun Başlangıç bölümüne bakın.
- Protokol arabelleği derleyicisi,
protoc
, 3.27.1 veya sonraki sürümler. Yükleme talimatları için derleyicinin yükleme kılavuzuna bakın. - Go ve gRPC için protokol arabelleği derleyici eklentileri. Bu eklentileri yüklemek için aşağıdaki komutları çalıştırın:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
PATH
değişkeninizi, protokol arabelleği derleyicisinin eklentileri bulabilmesi için güncelleyin:
export PATH="$PATH:$(go env GOPATH)/bin"
Kodu alın
Bu codelab, tamamen sıfırdan başlamanız gerekmemesi için uygulamanın kaynak kodunun tamamlayabileceğiniz bir iskeletini sağlar. Aşağıdaki adımlarda, protokol arabelleği derleyici eklentilerini kullanarak standart gRPC kodu oluşturma da dahil olmak üzere uygulamanın nasıl tamamlanacağı gösterilmektedir.
Öncelikle codelab çalışma dizinini oluşturun ve cd
içine gidin:
mkdir streaming-grpc-go-getting-started && cd streaming-grpc-go-getting-started
Codelab'i indirip ayıklayın:
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
Alternatif olarak, yalnızca codelab dizinini içeren .zip dosyasını indirebilir ve manuel olarak sıkıştırmasını açabilirsiniz.
Bir uygulamayı yazmayı atlamak isterseniz tamamlanmış kaynak kodunu GitHub'da bulabilirsiniz.
3. İletileri ve hizmetleri tanımlama
İlk adımınız, Protocol Buffers kullanarak uygulamanın gRPC hizmetini, RPC yöntemlerini, istek ve yanıt mesajı türlerini tanımlamaktır. Hizmetinizde sunulacaklar:
- Sunucunun uyguladığı ve istemcinin çağırdığı
ListFeatures
,RecordRoute
veRouteChat
adlı RPC yöntemleri. - Yukarıdaki yöntemler çağrıldığında istemci ile sunucu arasında değiştirilen veri yapıları olan
Point
,Feature
,Rectangle
,RouteNote
veRouteSummary
mesaj türleri.
Bu RPC yöntemleri ve mesaj türleri, sağlanan kaynak kodun routeguide/route_guide.proto
dosyasında tanımlanır.
Protocol Buffers genellikle protobuf olarak bilinir. gRPC terminolojisi hakkında daha fazla bilgi için gRPC'nin Temel kavramlar, mimari ve yaşam döngüsü başlıklı makalesine bakın.
Mesaj türlerini tanımlama
Kaynak kodun routeguide/route_guide.proto
dosyasında önce Point
mesaj türünü tanımlayın. Point
, haritadaki bir enlem-boylam koordinat çiftini temsil eder. Bu codelab'de koordinatlar için tam sayıları kullanın:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
1
ve 2
sayıları, message
yapısındaki her alan için benzersiz kimlik numaralarıdır.
Ardından, Feature
mesaj türünü tanımlayın. Bir Feature
, Point
ile belirtilen bir konumdaki bir şeyin adı veya posta adresi için string
alanını kullanır:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Ardından, enlem-boylam dikdörtgenini temsil eden bir Rectangle
mesajı gelir. Bu dikdörtgen, çapraz olarak zıt iki nokta ("lo" ve "hi") olarak gösterilir.
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
Ayrıca belirli bir noktada gönderilen mesajı temsil eden bir RouteNote
mesajı.
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
Ayrıca RouteSummary
mesajı da gereklidir. Bu mesaj, sonraki bölümde açıklanan bir RecordRoute
RPC'ye yanıt olarak alınır. Alınan bireysel puanların sayısı, algılanan özelliklerin sayısı ve her nokta arasındaki mesafenin kümülatif toplamı olarak kapsanan toplam mesafeyi içerir.
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;
}
Hizmet yöntemlerini tanımlama
Bir hizmeti tanımlamak için .proto dosyanızda adlandırılmış bir hizmet belirtirsiniz. route_guide.proto
dosyasında, uygulamanın hizmeti tarafından sağlanan bir veya daha fazla yöntemi tanımlayan RouteGuide
adlı bir service
yapısı bulunur.
Hizmet tanımınızda RPC
yöntemlerini tanımlayın, istek ve yanıt türlerini belirtin. Bu codelab bölümünde şunları tanımlayalım:
ListFeatures
Belirtilen Rectangle
içinde bulunan Feature
'ları alır. Dikdörtgen geniş bir alanı kaplayıp çok sayıda özellik içerebileceğinden sonuçlar tek seferde döndürülmek yerine (ör. tekrarlanan alan içeren bir yanıt mesajında) yayınlanır.
Bu RPC için uygun tür, sunucu tarafı yayın RPC'sidir: İstemci, sunucuya bir istek gönderir ve mesaj dizisini okumak için bir yayın alır. İstemci, döndürülen akıştan mesaj kalmayana kadar okur. Örneğimizde de görebileceğiniz gibi, yanıt türünden önce akış anahtar kelimesini yerleştirerek sunucu tarafı akış yöntemini belirtirsiniz.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
Geçilen bir rotadaki Point
akışını kabul eder ve geçiş tamamlandığında RouteSummary
döndürür.
Bu durumda istemci tarafı akış RPC'si uygun görünmektedir: İstemci, bir ileti dizisi yazar ve bunları sağlanan bir akışı kullanarak tekrar sunucuya gönderir. İstemci, mesajları yazmayı bitirdikten sonra sunucunun hepsini okumasını ve yanıtını döndürmesini bekler. İstemci tarafı yayın yöntemini, yayın anahtar kelimesini istek türünün önüne yerleştirerek belirtirsiniz.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
RouteChat
Bir rota geçilirken gönderilen RouteNote
akışını kabul ederken diğer RouteNote
'ları (ör. diğer kullanıcılardan) alır.
Bu, çift yönlü akışın tam olarak kullanım alanıdır. Çift yönlü akış RPC'sinde her iki taraf da okuma/yazma akışı kullanarak bir ileti dizisi gönderir. İki akış bağımsız olarak çalışır. Bu nedenle, istemciler ve sunucular istedikleri sırada okuma ve yazma işlemi yapabilir.
Örneğin, sunucu yanıtlarını yazmadan önce tüm istemci mesajlarını almayı bekleyebilir veya alternatif olarak bir mesajı okuyup ardından bir mesaj yazabilir ya da okuma ve yazma işlemlerinin başka bir kombinasyonunu kullanabilir.
Her akıştaki iletilerin sırası korunur. Bu tür bir yöntemi, hem istekten hem de yanıttan önce yayın anahtar kelimesini yerleştirerek belirtirsiniz.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
4. İstemci ve sunucu kodu oluşturma
Ardından, protokol arabellek derleyicisini kullanarak .proto
dosyasından hem istemci hem de sunucu için standart gRPC kodunu oluşturun. routeguide
dizininde şunu çalıştırın:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
Bu komut aşağıdaki dosyaları oluşturur:
route_guide.pb.go
, uygulamanın mesaj türlerini oluşturma ve verilerine erişme işlevlerini ve mesajları temsil eden türlerin tanımını içerir.route_guide_grpc.pb.go
: İstemcinin hizmetin uzak gRPC yöntemini çağırmak için kullandığı işlevleri ve sunucunun bu uzak hizmeti sağlamak için kullandığı işlevleri içerir.
Ardından, istemci bir istek gönderdiğinde sunucunun yanıt verebilmesi için yöntemleri sunucu tarafında uygulayacağız.
5. Hizmeti uygulama
Öncelikle RouteGuide
sunucuyu nasıl oluşturduğumuza bakalım. RouteGuide
hizmetimizin işini yapmasını sağlamak için iki bölüm vardır:
- Hizmet tanımımızdan oluşturulan hizmet arayüzünü uygulama: Hizmetimizin asıl "işini" yapma.
- İstemcilerden gelen istekleri dinlemek ve bunları doğru hizmet uygulamasına göndermek için bir gRPC sunucusu çalıştırma.
server/server.go
içinde RouteGuide'ı uygulayalım.
RouteGuide'ı uygulama
Oluşturulan RouteGuideService
arayüzünü uygulamamız gerekir. Uygulama bu şekilde görünür.
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 {
...
}
Her RPC uygulamasını ayrıntılı olarak inceleyelim.
Sunucu tarafında yayın RPC'si
Yayın RPC'lerimizden biriyle başlayın. ListFeatures
, sunucu tarafı akış RPC'si olduğundan istemcimize birden fazla Feature
göndermemiz gerekir.
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
}
Gördüğünüz gibi, yöntem parametrelerimizde basit istek ve yanıt nesneleri almak yerine bu kez bir istek nesnesi (istemcimizin Rectangle
içinde bulmak istediği Features
) ve yanıtlarımızı yazmak için özel bir RouteGuide_ListFeaturesServer
nesnesi alıyoruz. Bu yöntemde, döndürmemiz gereken sayıda Feature
nesnesi doldururuz ve bunları Send()
yöntemini kullanarak RouteGuide_ListFeaturesServer
'ye yazarız. Son olarak, basit RPC'mizde olduğu gibi, yanıt yazmayı bitirdiğimizi gRPC'ye bildirmek için nil
hatası döndürüyoruz. Bu çağrıda herhangi bir hata oluşursa sıfır olmayan bir hata döndürürüz. gRPC katmanı, bunu kablo üzerinden gönderilecek uygun bir RPC durumuna çevirir.
İstemci tarafı yayın RPC'si
Şimdi biraz daha karmaşık bir yönteme, istemci tarafı akış yöntemine RecordRoute
bakalım. Bu yöntemde istemciden bir Points
akışı alıp yolculuklarıyla ilgili bilgileri içeren tek bir RouteSummary
döndürüyoruz. Gördüğünüz gibi, bu kez yöntemde hiç istek parametresi yok. Bunun yerine, sunucunun hem mesaj okumak hem de yazmak için kullanabileceği bir RouteGuide_RecordRouteServer
akışı alır. Sunucu, Recv()
yöntemini kullanarak istemci mesajlarını alabilir ve SendAndClose()
yöntemini kullanarak tek yanıtını döndürebilir.
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
}
}
Yöntem gövdesinde, istemcimizin isteklerini bir istek nesnesine (bu örnekte Point
) tekrar tekrar okumak için RouteGuide_RecordRouteServer
'ın Recv()
yöntemini kullanırız. Bu işlem, başka mesaj kalmayana kadar devam eder. Sunucunun, her çağrıdan sonra Recv()
'dan döndürülen hatayı kontrol etmesi gerekir. Bu nil
ise akış hala iyidir ve okumaya devam edebilir; io.EOF
ise ileti akışı sona ermiştir ve sunucu RouteSummary
değerini döndürebilir. Başka bir değer varsa gRPC katmanı tarafından RPC durumuna çevrilmesi için hatayı "olduğu gibi" döndürürüz.
Çift yönlü akış RPC'si
Son olarak, çift yönlü akış RPC'mize RouteChat()
bakalım.
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
}
}
}
}
Bu kez, istemci tarafı akış örneğimizde olduğu gibi mesaj okumak ve yazmak için kullanılabilecek bir RouteGuide_RouteChatServer
akışı elde ederiz. Ancak bu kez, istemci mesaj akışına mesaj yazmaya devam ederken değerleri yöntemimizin akışı üzerinden döndürüyoruz. Burada okuma ve yazma söz dizimi, istemci akışı yöntemimize çok benzer. Ancak sunucu, birden fazla yanıt yazdığı için SendAndClose()
yerine akışın send()
yöntemini kullanır. Her iki taraf da diğer tarafın iletilerini her zaman yazıldıkları sırayla alsa da hem istemci hem de sunucu, iletileri herhangi bir sırada okuyup yazabilir. Akışlar tamamen bağımsız olarak çalışır.
Sunucuyu başlatma
Tüm yöntemlerimizi uyguladıktan sonra, istemcilerin hizmetimizi kullanabilmesi için bir gRPC sunucusu da başlatmamız gerekir. Aşağıdaki snippet'te, RouteGuide
hizmetimiz için bunu nasıl yaptığımız gösterilmektedir:
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()
bölgesinde adım adım neler oluyor:
lis, err := net.Listen(...)
kullanarak uzak istemci isteklerini dinlemek için kullanılacak TCP bağlantı noktasını belirtin. Varsayılan olarak uygulama, sunucu çalıştırılırken komut satırında--port
anahtarı iletilerek veyaport
değişkeniyle belirtildiği gibi50051
TCP bağlantı noktasını kullanır. TCP bağlantı noktası açılamazsa uygulama ölümcül bir hatayla sonlanır.grpc.NewServer(...)
kullanarak gRPC sunucusunun bir örneğini oluşturun ve bu örneğigrpcServer
olarak adlandırın.- Uygulamanın API hizmetini temsil eden bir yapı olan
routeGuideServer
içins.
adlı bir işaretçi oluşturun. - Diziyi doldurmak için
s.loadFeatures()
kullanıns.savedFeatures
. - Hizmet uygulamamızı gRPC sunucusuna kaydedin.
- İstemci istekleri için engelleme beklemesi yapmak üzere bağlantı noktası ayrıntılarımızla sunucuda
Serve()
işlevini çağırın. Bu işlem, süreç sonlandırılana veyaServe()
işlevi çağrılana kadar devam eder.Stop()
loadFeatures()
işlevi, koordinat-konum eşlemelerini server/testdata.go
kaynağından alır.
6. İstemciyi oluşturma
Şimdi istemci kodunu uygulayacağınız yer olan client/client.go
dosyasını düzenleyin.
Uzak hizmetin yöntemlerini çağırmak için öncelikle sunucuyla iletişim kuracak bir gRPC kanalı oluşturmamız gerekir. Bunu, istemcinin main()
işlevinde sunucunun hedef URI dizesini (bu örnekte yalnızca adres ve bağlantı noktası numarası) grpc.NewClient()
'ya ileterek oluştururuz:
// 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
değişkeniyle tanımlanan sunucunun adresi varsayılan olarak localhost:50051
'dir ve istemci çalıştırılırken komut satırındaki --addr
anahtarıyla geçersiz kılınabilir.
İstemcinin, TLS veya JWT kimlik bilgileri gibi kimlik doğrulama kimlik bilgileri gerektiren bir hizmete bağlanması gerekiyorsa istemci, gerekli kimlik bilgilerini içeren bir DialOptions
nesnesini grpc.NewClient
için parametre olarak iletebilir. RouteGuide
hizmeti herhangi bir kimlik bilgisi gerektirmez.
gRPC kanalı ayarlandıktan sonra, Go işlev çağrıları aracılığıyla RPC'ler gerçekleştirmek için bir istemci stub'ına ihtiyacımız vardır. Stub, uygulamanın .proto
dosyasından oluşturulan route_guide_grpc.pb.go
dosyası tarafından sağlanan NewRouteGuideClient
yöntemi kullanılarak alınır.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_streaming/routeguide")
client := pb.NewRouteGuideClient(conn)
Hizmet yöntemlerini çağırma
Şimdi de hizmet yöntemlerimizi nasıl çağırdığımıza bakalım. gRPC-Go'da RPC'ler, engelleme/eşzamanlı modda çalışır. Bu, RPC çağrısının sunucunun yanıt vermesini beklediği ve yanıt ya da hata döndüreceği anlamına gelir.
Sunucu tarafında yayın RPC'si
Burada, coğrafi Feature
nesnelerinin akışını döndüren sunucu tarafı akış yöntemi ListFeatures
çağrılır.
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())
}
Basit RPC'de olduğu gibi, yönteme bir bağlam ve bir istek iletiyoruz. Ancak yanıt nesnesi yerine RouteGuide_ListFeaturesClient
örneği alıyoruz. İstemci, sunucunun yanıtlarını okumak için RouteGuide_ListFeaturesClient
akışını kullanabilir. Sunucunun bir yanıt protokol arabelleği nesnesine (bu durumda Feature
) verdiği yanıtları, başka mesaj kalmayana kadar tekrar tekrar okumak için RouteGuide_ListFeaturesClient
'nın Recv()
yöntemini kullanırız: İstemcinin her çağrıdan sonra Recv()
'dan döndürülen err hatasını kontrol etmesi gerekir. nil
ise akış hâlâ iyi durumdadır ve okumaya devam edebilir. io.EOF
ise ileti akışı sona ermiştir. Aksi takdirde, err
üzerinden iletilen bir RPC hatası olmalıdır.
İstemci tarafı yayın RPC'si
İstemci tarafı akış yöntemi RecordRoute
, sunucu tarafı yöntemine benzer. Ancak bu yönteme yalnızca bir bağlam iletiriz ve RouteGuide_RecordRouteClient
akışı geri alırız. Bu akışı hem yazmak hem de okumak için kullanabiliriz.
// 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
, sunucuya istek göndermek için kullanabileceğimiz bir Send()
yöntemine sahiptir. Send()
kullanarak istemcimizin isteklerini akışa yazmayı bitirdikten sonra, yazma işlemini bitirdiğimizi ve yanıt beklediğimizi gRPC'ye bildirmek için akışta CloseAndRecv()
işlevini çağırmamız gerekir. RPC durumumuzu CloseAndRecv()
tarafından döndürülen hatadan alıyoruz. Durum nil
ise CloseAndRecv()
işlevinin ilk dönüş değeri geçerli bir sunucu yanıtı olur.
Çift yönlü akış RPC'si
Son olarak, çift yönlü akış RPC'mize RouteChat()
bakalım. RecordRoute
örneğinde olduğu gibi, yönteme yalnızca bir bağlam nesnesi iletiyor ve hem mesaj yazmak hem de okumak için kullanabileceğimiz bir akış geri alıyoruz. Ancak bu kez, sunucu ileti akışına ileti yazmaya devam ederken değerleri yöntemimizin akışı üzerinden döndürüyoruz.
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
Burada okuma ve yazma söz dizimi, istemci tarafı akış yöntemimize çok benzer. Tek fark, çağrımızı tamamladıktan sonra akışın CloseSend()
yöntemini kullanmamızdır. Her iki taraf da diğer tarafın iletilerini her zaman yazıldıkları sırayla alsa da hem istemci hem de sunucu, iletileri herhangi bir sırada okuyup yazabilir. Akışlar tamamen bağımsız olarak çalışır.
7. Deneyin
Uygulamanın çalışma dizininde aşağıdaki komutları çalıştırarak sunucu ve istemcinin birbirleriyle doğru şekilde çalıştığını onaylayın:
- Sunucuyu bir terminalde çalıştırın:
cd server go run .
- İstemciyi başka bir terminalden çalıştırma:
cd client go run .
Aşağıdakine benzer bir çıkış görürsünüz. Zaman damgaları, netlik için çıkarılmıştır:
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. Sırada ne var?
- Introduction to gRPC (gRPC'ye Giriş) ve Core concepts (Temel kavramlar) başlıklı makalelerden gRPC'nin işleyiş şeklini öğrenebilirsiniz.
- Temel bilgiler eğitimini tamamlayın.
- API referansını inceleyin.