การเริ่มต้นใช้งาน gRPC-Rust

1. บทนำ

ใน Codelab นี้ คุณจะได้ใช้ gRPC-Rust เพื่อสร้างไคลเอ็นต์และเซิร์ฟเวอร์ที่เป็นรากฐานของแอปพลิเคชันการกำหนดเส้นทางที่เขียนด้วย Rust

เมื่อจบบทแนะนำ คุณจะมีไคลเอ็นต์ที่เชื่อมต่อกับเซิร์ฟเวอร์ระยะไกลโดยใช้ การใช้งานอย่างเป็นทางการของ Rust ของ gRPC Protocol เพื่อดึงชื่อหรือที่อยู่ทางไปรษณีย์ของสถานที่หนึ่งๆ ที่พิกัดเฉพาะบนแผนที่ แอปพลิเคชันที่สมบูรณ์อาจใช้การออกแบบไคลเอ็นต์-เซิร์ฟเวอร์นี้เพื่อแสดงรายการหรือสรุปจุดที่น่าสนใจตามเส้นทาง

บริการนี้กำหนดไว้ในไฟล์ Protocol Buffers ซึ่งจะใช้เพื่อสร้างโค้ดเริ่มต้นสำหรับไคลเอ็นต์และเซิร์ฟเวอร์เพื่อให้สื่อสารกันได้ ซึ่งจะช่วยประหยัดเวลาและความพยายามในการใช้งานฟังก์ชันการทำงานดังกล่าว

โค้ดที่สร้างขึ้นนี้จะจัดการกับความซับซ้อนของการสื่อสารระหว่างเซิร์ฟเวอร์และไคลเอ็นต์ รวมถึงการซีเรียลไลซ์และดีซีเรียลไลซ์ข้อมูล

สิ่งที่คุณจะได้เรียนรู้

  • วิธีใช้ Protocol Buffers เพื่อกำหนด API ของบริการ
  • วิธีสร้างไคลเอ็นต์ที่ใช้ gRPC จากคำจำกัดความของ Protocol Buffer โดยใช้การสร้างโค้ดอัตโนมัติ
  • ความเข้าใจเกี่ยวกับการสื่อสารระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์ด้วย gRPC

Codelab นี้เหมาะสำหรับนักพัฒนาแอป Rust ที่เพิ่งเริ่มใช้ gRPC หรือต้องการทบทวน gRPC หรือผู้ที่สนใจสร้างระบบแบบกระจาย ไม่จำเป็นต้องมีประสบการณ์ในการใช้ gRPC มาก่อน

2. ก่อนเริ่มต้น

ข้อกำหนดเบื้องต้น

ตรวจสอบว่าคุณได้ติดตั้งสิ่งต่อไปนี้แล้ว

รับโค้ด

Codelab นี้มีโครงสร้างพื้นฐานของซอร์สโค้ดของแอปพลิเคชันให้คุณดำเนินการต่อได้เลยโดยไม่ต้องเริ่มต้นจากศูนย์ ขั้นตอนต่อไปนี้จะแสดงวิธีสร้างแอปพลิเคชันให้เสร็จสมบูรณ์ ซึ่งรวมถึงการใช้ปลั๊กอินคอมไพเลอร์บัฟเฟอร์โปรโตคอลเพื่อสร้างโค้ด gRPC เริ่มต้น

ขั้นแรก ให้สร้างไดเรกทอรีการทำงานของ Codelab แล้วเปลี่ยนไดเรกทอรีเป็นไดเรกทอรีนั้น

mkdir grpc-rust-getting-started && cd grpc-rust-getting-started

ดาวน์โหลดและแตกไฟล์ Codelab

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/2026.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-2026/codelabs/grpc-rust-getting-started/start_here

หรือคุณจะดาวน์โหลดไฟล์ .zip ที่มีเฉพาะไดเรกทอรี Codelab แล้วแตกไฟล์ด้วยตนเองก็ได้

ซอร์สโค้ดที่เสร็จสมบูรณ์แล้วพร้อมใช้งานบน GitHub หากคุณไม่ต้องการพิมพ์การใช้งาน

3. กำหนดบริการ

ขั้นตอนแรกคือการกำหนดบริการ gRPC ของแอปพลิเคชัน เมธอด RPC รวมถึงประเภทข้อความคำขอและการตอบกลับโดยใช้ Protocol Buffers บริการของคุณจะมีสิ่งต่อไปนี้

  • เมธอด RPC ชื่อ GetFeature ที่เซิร์ฟเวอร์ใช้งานและไคลเอ็นต์เรียก
  • ประเภทข้อความ Point และ Feature ซึ่งเป็นโครงสร้างข้อมูลที่ไคลเอ็นต์และเซิร์ฟเวอร์แลกเปลี่ยนกันเมื่อใช้เมธอด GetFeature ไคลเอ็นต์จะระบุพิกัดแผนที่เป็น Point ในคำขอ GetFeature ไปยังเซิร์ฟเวอร์ และเซิร์ฟเวอร์จะตอบกลับด้วย Feature ที่เกี่ยวข้องซึ่งอธิบายสิ่งที่อยู่ในพิกัดดังกล่าว

เมธอด RPC และประเภทข้อความทั้งหมดนี้จะกำหนดไว้ในไฟล์ proto/routeguide.proto ของซอร์สโค้ดที่ให้มา

Protocol Buffers หรือที่รู้จักกันโดยทั่วไปว่า protobuf ดูข้อมูลเพิ่มเติมเกี่ยวกับคำศัพท์ gRPC ได้ที่ แนวคิดหลัก สถาปัตยกรรม และวงจรชีวิตของ gRPC

เมธอดบริการ

ก่อนอื่น เรามากำหนดเมธอดบริการ แล้วกำหนดประเภทข้อความ Point และ Feature ไฟล์ proto/routeguide.proto มีโครงสร้าง service ชื่อ RouteGuide ซึ่งกำหนดเมธอดอย่างน้อย 1 รายการที่บริการของแอปพลิเคชันมีให้

เพิ่มเมธอด rpc ชื่อ GetFeature ภายในคำจำกัดความ RouteGuide ดังที่อธิบายไว้ก่อนหน้านี้ เมธอดนี้จะค้นหาชื่อหรือที่อยู่ของสถานที่จากชุดพิกัดที่กำหนด ดังนั้นให้ GetFeature แสดงผล Feature สำหรับ Point ที่กำหนด

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

นี่คือเมธอด RPC แบบ Unary อย่างง่าย ซึ่งเป็น RPC อย่างง่ายที่ไคลเอ็นต์ส่งคำขอไปยังเซิร์ฟเวอร์และรอการตอบกลับ เช่นเดียวกับการเรียกใช้ฟังก์ชันภายใน

ประเภทข้อความ

ในไฟล์ proto/routeguide.proto ของซอร์สโค้ด ให้กำหนดประเภทข้อความ Point ก่อน Point แสดงถึงคู่พิกัดละติจูด-ลองจิจูดบนแผนที่ สำหรับ Codelab นี้ ให้ใช้จำนวนเต็มสำหรับพิกัด

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

ตัวเลข 1 และ 2 เป็นหมายเลขรหัสที่ไม่ซ้ำกันสำหรับแต่ละช่องในโครงสร้าง message

จากนั้นกำหนดประเภทข้อความ Feature Feature ใช้ช่อง string สำหรับชื่อหรือที่อยู่ทางไปรษณีย์ของสิ่งหนึ่งๆ ในสถานที่ที่ระบุโดย Point

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

4. สร้างโค้ดไคลเอ็นต์และเซิร์ฟเวอร์

เราได้ให้โค้ดที่สร้างขึ้นจากไฟล์ .proto ในไดเรกทอรี generated/ แก่คุณแล้ว ซึ่งรวมถึงส่วนเพิ่มเติมทั้งหมดที่คุณทำไว้ข้างต้น อย่างไรก็ตาม เราขอใช้เวลาสักครู่เพื่ออธิบายวิธีการสร้างโค้ด

ไฟล์ .proto ของเราอธิบายโครงสร้างและฟังก์ชันทั้งหมดที่ไคลเอ็นต์หรือเซิร์ฟเวอร์ใช้ เราใช้สคริปต์บิลด์ Cargo (build.rs) ร่วมกับกล่อง grpc-protobuf-build เพื่อสร้างโค้ดนี้โดยอัตโนมัติ

ใน Cargo.toml เราจะเพิ่ม grpc-protobuf-build ไว้ใต้ [build-dependencies]

ใน build.rs เราจะกำหนดค่า grpc_protobuf_build::CodeGen เพื่อคอมไพล์ proto/routeguide.proto ลงในไดเรกทอรี generated/ บรรทัดสำคัญมีดังนี้

grpc_protobuf_build::CodeGen::new()
    .include("proto")
    .input("routeguide.proto")
    .output_dir("generated")
    .compile()
    .unwrap();

ซึ่งจะเรียกใช้การสร้างโค้ดของกล่อง grpc_protobuf_build โดยส่ง routeguide.proto ไปยังกล่องดังกล่าว เราได้รวมโค้ดนี้ไว้ในโค้ดบางส่วนเพื่อให้ทำงานเฉพาะเมื่อมีการส่งแฟล็กฟีเจอร์เท่านั้น เพื่อให้ระบบสร้างโค้ดใหม่เมื่อคุณต้องการเท่านั้น คุณไม่จำเป็นต้องเรียกใช้โค้ดนี้ในตอนนี้ เนื่องจากเราได้สร้างโค้ดให้คุณแล้ว

cargo build --bin routeguide-server --features regenerate_proto

เมื่อคุณเรียกใช้ cargo build build.rs จะคอมไพล์คำจำกัดความของบัฟเฟอร์โปรโตคอลลงในไดเรกทอรี generate/ ซึ่งรวมถึง

  • คำจำกัดความของโครงสร้างสำหรับประเภทข้อความ Point และ Feature
  • ลักษณะบริการ Tonic ที่เราจะต้องใช้งานสำหรับเซิร์ฟเวอร์: route_guide_server::RouteGuide
  • ประเภทไคลเอ็นต์ gRPC-Rust ที่เราจะใช้เพื่อเรียกเซิร์ฟเวอร์: route_guide_client::RouteGuideClient<T>

ดูข้อมูลเพิ่มเติมได้ที่คู่มือ protoc-gen-rust-grpc

จากนั้นเราจะใช้งานเมธอดบริการในเซิร์ฟเวอร์

5. ใช้งานบริการ

ใน src/server/server.rs เราสามารถนำโค้ดที่สร้างขึ้นมาใช้ได้ผ่านทางมาโคร include_generated_proto! ของ gRPC และนำเข้าลักษณะ RouteGuide และ Point

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_server::{RouteGuideServer, RouteGuide},
    Point, Feature,
};

เรามาเริ่มจากการกำหนดโครงสร้างเพื่อแสดงบริการของเรา โดยตอนนี้เราจะทำใน src/server/server.rs

#[derive(Debug)]
pub struct RouteGuideService {
    features: Vec<Feature>,
}

ตอนนี้เราต้องใช้งานลักษณะภาษา route_guide_server::RouteGuide จากโค้ดที่สร้างขึ้น

RPC แบบ Unary อย่างง่าย

RouteGuideService ใช้งานเมธอดบริการทั้งหมดของเรา ฟังก์ชัน get_feature ในฝั่งเซิร์ฟเวอร์เป็นที่ที่การทำงานหลักเกิดขึ้น โดยฟังก์ชันนี้จะรับข้อความ Point จากไคลเอ็นต์และแสดงผลข้อมูลสถานที่ที่เกี่ยวข้องจากรายการสถานที่ที่รู้จักในข้อความ Feature นี่คือการใช้งานฟังก์ชันใน src/server/server.rs

#[tonic::async_trait]
impl RouteGuide for RouteGuideService {
    async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
        println!("GetFeature = {:?}", request);
        let requested_point = request.get_ref();
        for feature in self.features.iter() {
            if feature.location().latitude() == requested_point.latitude() {
                if feature.location().longitude() == requested_point.longitude(){
                    return Ok(Response::new(feature.clone()))
                };
            };    
        }
        Ok(Response::new(Feature::default()))
    }
}

เมื่อใช้งานเมธอดนี้แล้ว เราต้องเริ่มต้นเซิร์ฟเวอร์ gRPC ด้วยเพื่อให้ไคลเอ็นต์ใช้บริการของเราได้จริง แทนที่ main() ด้วยโค้ดนี้

#[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() ทีละขั้นตอน:

  1. ระบุพอร์ตที่เราต้องการใช้เพื่อรับฟังคำขอของไคลเอ็นต์
  2. สร้าง RouteGuideService ที่โหลดฟีเจอร์โดยการเรียกใช้ฟังก์ชันตัวช่วย load()
  3. สร้างอินสแตนซ์ของเซิร์ฟเวอร์ gRPC โดยใช้ RouteGuideServer::new() ด้วยบริการที่เราสร้างขึ้น
  4. ลงทะเบียนการใช้งานบริการของเรากับเซิร์ฟเวอร์ gRPC
  5. เรียกใช้ serve() ในเซิร์ฟเวอร์พร้อมรายละเอียดพอร์ตเพื่อรอแบบบล็อกจนกว่าจะมีการหยุดกระบวนการ

6. สร้างไคลเอ็นต์

ในส่วนนี้ เราจะดูการสร้างไคลเอ็นต์ Rust สำหรับบริการ RouteGuide ใน src/client/client.rs

เช่นเดียวกับที่เราทำใน src/server/server.rs เราสามารถนำโค้ดที่สร้างขึ้นมาใช้ได้ผ่านทางมาโคร include_generated_proto! ของ gRPC และนำเข้าประเภท RouteGuideClient

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_client::RouteGuideClient,
    Point,
};

เรียกใช้เมธอดบริการ

ใน gRPC-Rust, RPC จะทำงานแบบอะซิงโครนัสและไม่บล็อก โดยใช้ไวยากรณ์ async/await ของ Rust เพื่อรอการตอบกลับจากเซิร์ฟเวอร์

หากต้องการเรียกใช้เมธอดบริการ ก่อนอื่นเราต้องสร้าง Channel โดยใช้ Channel::builder() โดยระบุที่อยู่เซิร์ฟเวอร์ (dns:///[::1]:10000) และข้อมูลเข้าสู่ระบบการเชื่อมต่อ (LocalChannelCredentials) จากนั้นส่งช่องไปยัง RouteGuideClient::new() เพื่อสร้างอินสแตนซ์ไคลเอ็นต์

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new gRPC channel
    let channel = Channel::builder(
        "dns:///[::1]:10000",
        Arc::new(LocalChannelCredentials::new()),
    )
    .build();

    // Create a new client
    let client = RouteGuideClient::new(channel);



}

ในฟังก์ชันนี้ RouteGuideClient::new() จะผูกช่องทั่วไปกับ Stub ไคลเอ็นต์ที่สร้างขึ้นซึ่งใช้งานเมธอดที่กำหนดไว้ในคำจำกัดความบริการ .proto

RPC อย่างง่าย

การเรียกใช้ RPC อย่างง่าย GetFeature นั้นง่ายเหมือนกับการเรียกใช้เมธอดภายใน เพิ่มโค้ดนี้ใน main()

println!("*** SIMPLE RPC ***");
let point = proto!(Point {
    latitude: 409_146_138,
    longitude: -746_188_906,
});
let response = client
    .get_feature(point)
    .await
    .expect("RPC error");

ใน gRPC-Rust เราจะส่งข้อความ protobuf Point ไปยัง client.get_feature(point) โดยตรง การรออนาคตที่แสดงผลจะให้การตอบกลับ Feature โดยตรงโดยไม่จำเป็นต้องเรียกใช้เมธอดเพิ่มเติม

จากนั้นพิมพ์ช่องจากการตอบกลับ

println!(
    "Response = Name = \"{}\", Latitude = {}, Longitude = {}",
    response.name(),
    response.location().latitude(),
    response.location().longitude()
);

โดยรวมแล้ว ฟังก์ชัน main() ของไคลเอ็นต์ควรมีลักษณะดังนี้

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Create a new gRPC channel
    let channel = Channel::builder(
        "dns:///[::1]:10000",
        Arc::new(LocalChannelCredentials::new()),
    )
    .build();

    // 2. Instantiate the generated RouteGuideClient
    let client = RouteGuideClient::new(channel);

    println!("*** SIMPLE RPC ***");
    let point = proto!(Point {
        latitude: 409_146_138,
        longitude: -746_188_906,
    });
    let response = client
        .get_feature(point)
        .await
        .expect("RPC error");

    println!(
        "Response = Name = \"{}\", Latitude = {}, Longitude = {}",
        response.name(),
        response.location().latitude(),
        response.location().longitude()
    );
    Ok(())
}

7. ลองเลย

หากต้องการเรียกใช้ไคลเอ็นต์และเซิร์ฟเวอร์ ให้ตรวจสอบก่อนว่ามีการกำหนดเป้าหมายไบนารีทั้ง 2 รายการไว้ใน Cargo.toml

[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"

[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"

จากนั้นเรียกใช้คำสั่งต่อไปนี้จากไดเรกทอรีการทำงาน

  1. เรียกใช้เซิร์ฟเวอร์ในเทอร์มินัลหนึ่ง
cargo run --bin routeguide-server
  1. เรียกใช้ไคลเอ็นต์จากเทอร์มินัลอื่น
cargo run --bin routeguide-client

คุณจะเห็นเอาต์พุตลักษณะนี้ โดยเราได้ละเว้นการประทับเวลาเพื่อความชัดเจน

*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906

8. ขั้นตอนถัดไป

9. ผู้ร่วมให้ข้อมูลใน Codelab นี้

  • Cathy Zhao
  • Lucio Franco
  • Arvind Bright
  • Nathaniel Ford