gRPC-Rust ile Başlarken - Akış

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:

  • GCC. Buradaki talimatları uygulayın.
  • Rust, en yeni sürüm. Yükleme talimatlarını buradan inceleyin.

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 ve RouteChat 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 ve RouteSummary 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 ve RouteSummary 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?

  1. İstemci isteklerini dinlemek için kullanmak istediğimiz bağlantı noktasını belirtin.
  2. Yüklü özelliklere sahip bir RouteGuideService oluşturma
  3. Oluşturduğumuz hizmeti kullanarak RouteGuideServer::new() ile gRPC sunucusunun bir örneğini oluşturun.
  4. Hizmet uygulamamızı gRPC sunucusuna kaydedin.
  5. İş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:

  1. Sunucuyu bir terminalde çalıştırın:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server 
  1. İ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?