1. Giriş
Bu codelab'de, Rust ile yazılmış bir rota eşleme uygulamasının temelini oluşturan bir istemci ve sunucu oluşturmak için gRPC-Rust'ı 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 bilgilerini tazelemek isteyen Rust geliştiricilerin 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:
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 arabellek 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-rust-getting-started && cd streaming-grpc-rust-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-rust-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 proto/routeguide.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
Öncelikle RPC'lerimiz tarafından kullanılacak mesajlarımızı tanımlayalım. 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
Önce hizmetimizi, ardından mesajlarımızı tanımlayalım. Bir hizmeti tanımlamak için .proto
dosyanızda adlandırılmış bir hizmet belirtirsiniz. proto/routeguide.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 stream
anahtar kelimesini yerleştirerek sunucu tarafı yayın 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ı yine sağlanan bir akışı kullanarak sunucuya gönderir. İstemci, mesajları yazmayı bitirdikten sonra sunucunun hepsini okumasını ve yanıtını döndürmesini bekler. İstemci tarafı akış yöntemini, istek türünden önce stream
anahtar kelimesini 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 isteğin hem de yanıtın önüne stream
anahtar kelimesini yerleştirerek belirtirsiniz.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
4. İstemci ve sunucu kodunu oluşturma
Oluşturulan dizindeki .proto
dosyasından oluşturulan kodu size zaten verdik.
.proto
dosyasından nasıl kod oluşturacağınızı öğrenmek veya .proto
dosyasında değişiklik yapıp bunları test etmek istiyorsanız bu talimatları inceleyin.
Oluşturulan kod şunları içerir:
Point
,Feature
,Rectangle
,RouteNote
veRouteSummary
mesaj türleri için yapı tanımları.- Uygulamamız gereken bir hizmet özelliği:
route_guide_server::RouteGuide
. - Sunucuyu çağırmak için kullanacağımız istemci türü:
route_guide_client::RouteGuideClient<T>
.
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ı 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 yöntem uygulamasına göndermek için bir gRPC sunucusu çalıştırma.
src/server/server.rs
içinde, oluşturulan kodu gRPC'nin include_generated_proto!
makrosu aracılığıyla kapsam içine alabilir, RouteGuide
özelliğini ve Point
öğesini içe aktarabiliriz.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
pub use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature, Rectangle, RouteNote, RouteSummary
};
Hizmetimizi temsil edecek bir yapı tanımlayarak başlayabiliriz. Şu anda bu işlemi src/server/server.rs
üzerinde yapabilirsiniz:
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
Şimdi, oluşturduğumuz koddan route_guide_server::RouteGuide
özelliğini uygulamamız gerekiyor.
RouteGuide'ı uygulama
Oluşturulan RouteGuide
arayüzünü uygulamamız gerekir. Uygulama bu şekilde görünür. Bu zaten şablonda var.
#[tonic::async_trait] impl RouteGuide for RouteGuideService { async fn list_features( &self, request: Request<Rectangle>, ) -> Result<Response<ListFeaturesStream>, Status> { ... } async fn record_route( &self, request: Request<tonic::Streaming<Point>>, ) -> Result<Response<RouteSummary>, Status> { ... } async fn route_chat( &self, request: Request<tonic::Streaming<RouteNote>>, ) -> Result<Response<RouteChatStream>, Status> { ... } }
Her RPC uygulamasını ayrıntılı olarak inceleyelim.
Sunucu tarafında yayın RPC'si
ListFeatures
ile başlayalım. Bu, sunucu tarafı yayın RPC'si olduğundan istemcimize birden fazla Feature
göndermemiz gerekir.
async fn list_features(
&self,
request: Request<Rectangle>,
) -> Result<Response<ListFeaturesStream>, Status> {
println!("ListFeatures = {:?}", request);
let (tx, rx) = mpsc::channel(4);
let features = self.features.clone();
tokio::spawn(async move {
for feature in &features[..] {
if in_range(&feature.location().to_owned(), request.get_ref()) {
println!(" => send {feature:?}");
tx.send(Ok(feature.clone())).await.unwrap();
}
}
println!(" /// done sending");
});
let output_stream = ReceiverStream::new(rx);
Ok(Response::new(Box::pin(output_stream)))
}
Gördüğünüz gibi, bir istek nesnesi (istemcimizin Rectangle
içinde Features
bulmak istediği) alıyoruz. Bu kez, bir değer akışı döndürmemiz gerekiyor. Bir kanal oluşturup yeni bir eşzamansız görev başlatıyoruz. Bu görevde bir arama gerçekleştirip kısıtlamalarımızı karşılayan özellikleri kanala gönderiyoruz. Kanalın akış kısmı, arayana tonic::Response
içine alınarak döndürülür.
İ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. Giriş olarak bir akış alır. Sunucu, bu akışı hem iletileri okumak hem de yazmak için kullanabilir. next()
yöntemini kullanarak istemci mesajlarını yineleyebilir ve tek yanıtını döndürebilir.
async fn record_route(
&self,
request: Request<tonic::Streaming<Point>>,
) -> Result<Response<RouteSummary>, Status> {
println!("RecordRoute");
let mut stream = request.into_inner();
let mut summary = RouteSummary::default();
let mut last_point = None;
let now = Instant::now();
while let Some(point) = stream.next().await {
let point = point?;
println!(" ==> Point = {point:?}");
// Increment the point count
summary.set_point_count(summary.point_count() + 1);
// Find features
for feature in &self.features[..] {
if feature.location().latitude() == point.latitude() {
if feature.location().longitude() == point.longitude(){
summary.set_feature_count(summary.feature_count() + 1);
}
}
}
// Calculate the distance
if let Some(ref last_point) = last_point {
let new_dist = summary.distance() + calc_distance(last_point, &point);
summary.set_distance(new_dist);
}
last_point = Some(point);
}
summary.set_elapsed_time(now.elapsed().as_secs() as i32);
Ok(Response::new(summary))
}
Yöntem gövdesinde, istemcimizin isteklerini istek nesnesine (bu örnekte Point
) tekrar tekrar okumak için akışın next()
yöntemini kullanırız. Bu işlem, başka mesaj kalmayana kadar devam eder. Bu değer None ise akış hala iyi durumdadır ve okumaya devam edebilir.
Çift yönlü akış RPC'si
Son olarak, çift yönlü akış RPC'mize RouteChat()
göz atalım.
async fn route_chat(
&self,
request: Request<tonic::Streaming<RouteNote>>,
) -> Result<Response<RouteChatStream>, Status> {
println!("RouteChat");
let mut notes: HashMap<(i32, i32), Vec<RouteNote>> = HashMap::new();
let mut stream = request.into_inner();
let output = async_stream::try_stream! {
while let Some(note) = stream.next().await {
let note = note?;
let location = note.location();
let key = (location.latitude(), location.longitude());
let location_notes = notes.entry(key).or_insert(vec![]);
location_notes.push(note);
for note in location_notes {
yield note.clone();
}
}
};
Ok(Response::new(Box::pin(output)))
}
Bu kez, istemci tarafı akış örneğimizde olduğu gibi, mesaj okumak ve yazmak için kullanılabilecek bir 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, sunucunun RouteChatStream
döndürmesi dışında istemci akışı yöntemimize çok benzer. 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.
Akışın hata döndürebileceğini belirten try_stream!
kullanarak output
akışını oluştururuz.
Sunucuyu başlatma
Bu yöntemi uyguladıktan sonra, istemcilerin hizmetimizi kullanabilmesi için bir gRPC sunucusu da başlatmamız gerekir. main()
kısmını doldurun.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:10000".parse().unwrap();
println!("RouteGuideServer listening on: {addr}");
let route_guide = RouteGuideService {
features: load(),
};
let svc = RouteGuideServer::new(route_guide);
Server::builder().add_service(svc).serve(addr).await?;
Ok(())
}
main()
bölgesinde adım adım neler oluyor?
- İstemci isteklerini dinlemek için kullanmak istediğimiz bağlantı noktasını belirtin.
- Yüklü özelliklere sahip bir
RouteGuideService
oluşturma - Oluşturduğumuz hizmeti kullanarak
RouteGuideServer::new()
ile gRPC sunucusunun bir örneğini oluşturun. - Hizmet uygulamamızı gRPC sunucusuna kaydedin.
- İşlem sonlandırılana kadar engelleme beklemesi yapmak için sunucuda bağlantı noktası ayrıntılarımızla birlikte
serve()
komutunu çağırın.
6. İstemciyi oluşturma
Bu bölümde, src/client/client.rs
dilinde RouteGuide hizmetimiz için bir Rust istemcisi oluşturmayı ele alacağız.
Öncelikle, oluşturulan kodu kapsam içine alın.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::route_guide_client::RouteGuideClient;
use grpc_pb::{Point, Rectangle, RouteNote};
Hizmet yöntemlerini çağırma
Şimdi de hizmet yöntemlerimizi nasıl çağırdığımıza bakalım. gRPC-Rust'ta 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.
async fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
let rectangle = proto!(Rectangle {
lo: proto!(Point {
latitude: 400_000_000,
longitude: -750_000_000,
}),
hi: proto!(Point {
latitude: 420_000_000,
longitude: -730_000_000,
}),
});
let mut stream = client
.list_features(Request::new(rectangle))
.await?
.into_inner();
while let Some(feature) = stream.message().await? {
println!("FEATURE: Name = \"{}\", Lat = {}, Lon = {}",
feature.name(),
feature.location().latitude(),
feature.location().longitude());
}
Ok(())
}
Yönteme bir istek iletiriz ve ListFeaturesStream
örneğini geri alırız. İstemci, sunucunun yanıtlarını okumak için ListFeaturesStream
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 ListFeaturesStream
'nın message()
yöntemini kullanırız.
İstemci tarafı yayın RPC'si
Burada record_route
için nokta vektörünü akışa dönüştürüyoruz. Ardından bu akışı istek olarak record_route()
'ya iletiriz ve akış sunucu tarafından tamamen işlendikten sonra tek bir RouteSummary
yanıtı alırız.
async fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
let mut rng = rand::rng();
let point_count: i32 = rng.random_range(2..100);
let mut points = vec![];
for _ in 0..=point_count {
points.push(random_point(&mut rng))
}
println!("Traversing {} points", points.len());
let request = Request::new(tokio_stream::iter(points));
match client.record_route(request).await {
Ok(response) => {
let response = response.into_inner();
println!("SUMMARY: Feature Count = {}, Distance = {}", response.feature_count(), response.distance())},
Err(e) => println!("something went wrong: {e:?}"),
}
Ok(())
}
Çift yönlü akış RPC'si
Son olarak, çift yönlü akış RPC'mize RouteChat()
göz atalım. Yönteme, yazdığımız bir akış isteğini iletiyoruz ve mesajları okuyabileceğimiz bir akış geri alıyoruz. Bu kez, sunucu mesaj akışına mesaj yazmaya devam ederken değerleri yöntemimizin akışı üzerinden döndürüyoruz.
async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
let start = time::Instant::now();
let outbound = async_stream::stream! {
let mut interval = time::interval(Duration::from_secs(1));
for _ in 0..10 {
let time = interval.tick().await;
let elapsed = time.duration_since(start);
let note = proto!(RouteNote {
location: proto!(Point {
latitude: 409146138 + elapsed.as_secs() as i32,
longitude: -746188906,
}),
message: format!("at {elapsed:?}"),
});
yield note;
}
};
let response = client.route_chat(Request::new(outbound)).await?;
let mut inbound = response.into_inner();
while let Some(note) = inbound.message().await? {
println!("Note: Latitude = {}, Longitude = {}, Message = \"{}\"",
note.location().latitude(),
note.location().longitude(),
note.message());
}
Ok(())
}
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.
Yardımcı yöntemleri çağırma
Hizmet yöntemlerini çağırmak için öncelikle sunucuyla iletişim kuracak bir kanal oluşturmamız gerekir. Bunu oluşturmak için önce bir uç nokta oluşturur, bu uç noktaya bağlanır ve RouteGuideClient::new()
'ya bağlanıldığında oluşturulan kanalı aşağıdaki gibi iletiriz:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create endpoint to connect to
let endpoint = Endpoint::new("http://[::1]:10000")?;
let channel = endpoint.connect().await?;
// Create a new client
let mut client = RouteGuideClient::new(channel);
Ok(())
}
main()
içinde, az önce oluşturduğumuz yöntemleri yürütün.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create endpoint to connect to
let endpoint = Endpoint::new("http://[::1]:10000")?;
let channel = endpoint.connect().await?;
// Create a new client
let mut client = RouteGuideClient::new(channel);
println!("\n*** SERVER STREAMING ***");
print_features(&mut client).await?;
println!("\n*** CLIENT STREAMING ***");
run_record_route(&mut client).await?;
println!("\n*** BIDIRECTIONAL STREAMING ***");
run_route_chat(&mut client).await?;
Ok(())
}
7. Deneyin
Öncelikle istemcimizi ve sunucumuzu çalıştırmak için bunları sandığımıza ikili hedef olarak ekleyelim. Cargo.toml dosyamızı buna göre düzenlememiz gerekiyor:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Her projede olduğu gibi, kodumuzun çalışması için gerekli olan bağımlılıkları da düşünmemiz gerekir. Rust projelerinde bağımlılıklar Cargo.toml
içinde yer alır. Gerekli bağımlılıkları Cargo.toml
dosyasında listeledik.
Ardından, çalışma dizinlerimizden aşağıdaki komutları yürütün:
- Sunucuyu bir terminalde çalıştırın:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server
- İstemciyi başka bir terminalden çalıştırma:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-client
Şuna benzer bir çıkış görürsünüz:
*** SERVER STREAMING *** FEATURE: Name = "Patriots Path, Mendham, NJ 07945, USA", Lat = 407838351, Lon = -746143763 FEATURE: Name = "101 New Jersey 10, Whippany, NJ 07981, USA", Lat = 408122808, Lon = -743999179 FEATURE: Name = "U.S. 6, Shohola, PA 18458, USA", Lat = 413628156, Lon = -749015468 ... *** CLIENT STREAMING *** Traversing 86 points SUMMARY: Feature Count = 0, Distance = 803709356 *** BIDIRECTIONAL STREAMING *** Note: Latitude = 409146138, Longitude = -746188906, Message = "at 112.45µs" Note: Latitude = 409146139, Longitude = -746188906, Message = "at 1.00011245s" Note: Latitude = 409146140, Longitude = -746188906, Message = "at 2.00011245s"
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.