1. บทนำ
ในโค้ดแล็บนี้ คุณจะได้ใช้ gRPC-Rust เพื่อสร้างไคลเอ็นต์และเซิร์ฟเวอร์ซึ่งเป็นรากฐานของแอปพลิเคชันการแมปเส้นทางที่เขียนด้วย Rust
เมื่อจบบทแนะนำนี้ คุณจะมีไคลเอ็นต์ที่เชื่อมต่อกับเซิร์ฟเวอร์ระยะไกลโดยใช้ gRPC เพื่อรับชื่อหรือที่อยู่ไปรษณีย์ของสิ่งที่อยู่ ณ พิกัดที่เฉพาะเจาะจงบนแผนที่ แอปพลิเคชันที่สมบูรณ์อาจใช้การออกแบบไคลเอ็นต์-เซิร์ฟเวอร์นี้เพื่อแจงนับหรือสรุปจุดที่น่าสนใจตามเส้นทาง
บริการนี้กำหนดไว้ในไฟล์ Protocol Buffers ซึ่งจะใช้เพื่อสร้างโค้ดมาตรฐานสำหรับไคลเอ็นต์และเซิร์ฟเวอร์เพื่อให้สื่อสารกันได้ ซึ่งจะช่วยประหยัดเวลาและแรงในการติดตั้งใช้งานฟังก์ชันดังกล่าว
โค้ดที่สร้างขึ้นนี้ไม่เพียงแต่จัดการความซับซ้อนของการสื่อสารระหว่างเซิร์ฟเวอร์และไคลเอ็นต์เท่านั้น แต่ยังจัดการการซีเรียลไลซ์และการดีซีเรียลไลซ์ข้อมูลด้วย
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้ Protocol Buffers เพื่อกำหนด API ของบริการ
- วิธีสร้างไคลเอ็นต์และเซิร์ฟเวอร์ที่ใช้ gRPC จากคำจำกัดความของ Protocol Buffers โดยใช้การสร้างโค้ดอัตโนมัติ
- ความเข้าใจเกี่ยวกับการสื่อสารระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์ด้วย gRPC
Codelab นี้มีไว้สำหรับนักพัฒนา Rust ที่เพิ่งเริ่มใช้ gRPC หรือต้องการทบทวน gRPC หรือใครก็ตามที่สนใจสร้างระบบแบบกระจาย ไม่จำเป็นต้องมีประสบการณ์เกี่ยวกับ gRPC มาก่อน
2. ก่อนเริ่มต้น
ข้อกำหนดเบื้องต้น
ตรวจสอบว่าคุณได้ติดตั้งสิ่งต่อไปนี้แล้ว
รับโค้ด
Codelab นี้มีโครงสร้างของซอร์สโค้ดของแอปพลิเคชันเพื่อให้คุณทำต่อได้ คุณจึงไม่ต้องเริ่มต้นจากศูนย์ ขั้นตอนต่อไปนี้จะแสดงวิธีส่งแอปพลิเคชันให้เสร็จสมบูรณ์ ซึ่งรวมถึงการใช้ปลั๊กอินคอมไพเลอร์ Protocol Buffer เพื่อสร้างโค้ด gRPC ที่ซ้ำกัน
ก่อนอื่น ให้สร้างไดเรกทอรีการทำงานของ Codelab แล้วใช้คำสั่ง cd เพื่อเข้าไปในไดเรกทอรีดังกล่าว
mkdir grpc-rust-getting-started && cd grpc-rust-getting-started
ดาวน์โหลดและแตกไฟล์ Codelab โดยทำดังนี้
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-getting-started/start_here
หรือคุณจะดาวน์โหลดไฟล์ .zip ที่มีเฉพาะไดเรกทอรี Codelab แล้วแตกไฟล์ด้วยตนเองก็ได้
ซอร์สโค้ดที่เสร็จสมบูรณ์แล้วพร้อมใช้งานใน GitHub หากคุณไม่ต้องการพิมพ์การติดตั้งใช้งาน
3. กำหนดบริการ
ขั้นตอนแรกคือการกำหนดบริการ gRPC ของแอปพลิเคชัน เมธอด RPC รวมถึงประเภทข้อความคำขอและการตอบกลับโดยใช้ Protocol Buffers บริการของคุณจะให้ข้อมูลต่อไปนี้
- เมธอด RPC ที่ชื่อ
GetFeature
ซึ่งเซิร์ฟเวอร์นำมาใช้และไคลเอ็นต์เรียกใช้ - ประเภทข้อความ
Point
และFeature
ซึ่งเป็นโครงสร้างข้อมูลที่แลกเปลี่ยนระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์เมื่อใช้เมธอดGetFeature
ไคลเอ็นต์จะระบุพิกัดแผนที่เป็นPoint
ในคำขอGetFeature
ไปยังเซิร์ฟเวอร์ และเซิร์ฟเวอร์จะตอบกลับด้วยFeature
ที่เกี่ยวข้องซึ่งอธิบายสิ่งที่อยู่ในพิกัดเหล่านั้น
วิธีการ RPC นี้และประเภทข้อความของวิธีการนี้จะกำหนดไว้ในไฟล์ proto/route_guide.proto
ของซอร์สโค้ดที่ระบุ
Protocol Buffers เรียกกันโดยทั่วไปว่า protobuf ดูข้อมูลเพิ่มเติมเกี่ยวกับคำศัพท์ gRPC ได้ที่แนวคิดหลัก สถาปัตยกรรม และวงจรของ gRPC
วิธีการบริการ
ก่อนอื่นเรามากำหนดเมธอดบริการ แล้วกำหนดประเภทข้อความ Point
และ Feature
กัน ไฟล์ proto/routeguide.proto
มีโครงสร้าง service
ชื่อ RouteGuide
ซึ่งกำหนดวิธีการอย่างน้อย 1 วิธีที่บริการของแอปพลิเคชันมีให้
เพิ่มrpc
method 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 แบบเอกภาค ซึ่งเป็น RPC อย่างง่ายที่ไคลเอ็นต์ส่งคำขอไปยังเซิร์ฟเวอร์และรอให้เซิร์ฟเวอร์ส่งการตอบกลับกลับมา เหมือนกับการเรียกฟังก์ชันในเครื่อง
ประเภทข้อความ
ในproto/route_guide.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
ในไดเรกทอรีที่สร้างแล้วแก่คุณแล้ว
เช่นเดียวกับโปรเจ็กต์อื่นๆ เราต้องพิจารณาการขึ้นต่อกันที่จำเป็นสำหรับโค้ดของเรา สำหรับโปรเจ็กต์ Rust การอ้างอิงจะอยู่ใน Cargo.toml
เราได้แสดงรายการทรัพยากร Dependency ที่จำเป็นไว้ในไฟล์ Cargo.toml
แล้ว
หากต้องการดูวิธีสร้างโค้ดจากไฟล์ .proto
ด้วยตนเอง โปรดดูวิธีการเหล่านี้
โค้ดที่สร้างขึ้นประกอบด้วย
- คำจำกัดความของโครงสร้างสำหรับประเภทข้อความ
Point
และFeature
- ลักษณะบริการที่เราต้องใช้คือ
route_guide_server::RouteGuide
- ประเภทไคลเอ็นต์ที่เราจะใช้เพื่อเรียกเซิร์ฟเวอร์:
route_guide_client::RouteGuideClient<T>
จากนั้นเราจะใช้เมธอด GetFeature
ในฝั่งเซิร์ฟเวอร์เพื่อให้เมื่อไคลเอ็นต์ส่งคำขอ เซิร์ฟเวอร์จะตอบกลับได้
5. ติดตั้งใช้งานบริการ
ใน src/server/server.rs
เราสามารถนำโค้ดที่สร้างขึ้นมาไว้ในขอบเขตผ่านมาโคร include_generated_proto!
ของ gRPC และนำเข้าลักษณะ RouteGuide
และ Point
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
pub 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
จากโค้ดที่สร้างขึ้น
Unary RPC
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()))
}
}
ในเมธอด ให้ป้อนข้อมูลที่เหมาะสมสำหรับ Point
ที่ระบุลงในออบเจ็กต์ Feature
แล้วส่งคืน
เมื่อใช้วิธีนี้แล้ว เราก็ต้องเริ่มเซิร์ฟเวอร์ 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()
มีดังนี้
- ระบุพอร์ตที่เราต้องการใช้เพื่อรอรับคำขอของไคลเอ็นต์
- สร้าง
RouteGuideService
โดยโหลดฟีเจอร์ด้วยการเรียกใช้ฟังก์ชันตัวช่วยload()
- สร้างอินสแตนซ์ของเซิร์ฟเวอร์ gRPC โดยใช้
RouteGuideServer::new()
โดยใช้บริการที่เราสร้างขึ้น - ลงทะเบียนการติดตั้งใช้งานบริการกับเซิร์ฟเวอร์ gRPC
- เรียกใช้
serve()
ในเซิร์ฟเวอร์พร้อมรายละเอียดพอร์ตเพื่อรอการบล็อกจนกว่าจะมีการสิ้นสุดกระบวนการ
6. สร้างไคลเอ็นต์
ในส่วนนี้ เราจะมาดูการสร้างไคลเอ็นต์ Rust สำหรับบริการ RouteGuide ใน src/client/client.rs
กัน
เช่นเดียวกับใน src/server/server.rs
เราสามารถนำโค้ดที่สร้างขึ้นมาไว้ในขอบเขตผ่านมาโคร include_generated_code!
ของ gRPC และนำเข้าประเภท RouteGuideClient
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_client::RouteGuideClient,
Point,
};
วิธีการเรียกใช้บริการ
ใน gRPC-Rust, RPC จะทํางานในโหมดบล็อก/ซิงโครนัส ซึ่งหมายความว่าการเรียก RPC จะรอให้เซิร์ฟเวอร์ตอบกลับ และจะส่งคืนการตอบกลับหรือข้อผิดพลาด
หากต้องการเรียกใช้เมธอดบริการ เราต้องสร้างแชแนลเพื่อสื่อสารกับเซิร์ฟเวอร์ก่อน เราสร้างสิ่งนี้โดยการสร้างปลายทางก่อน จากนั้นเชื่อมต่อกับปลายทางนั้น และส่งช่องที่สร้างขึ้นเมื่อเชื่อมต่อกับ RouteGuideClient::new()
ดังนี้
#[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(())
}
ในฟังก์ชันนี้ เมื่อสร้างไคลเอ็นต์ เราจะรวมแชแนลทั่วไปที่สร้างไว้ด้านบนเข้ากับโค้ด 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(Request::new(point))
.await?.into_inner();
Ok(())
ดังที่คุณเห็น เราเรียกใช้เมธอดใน Stub ที่เราได้รับก่อนหน้านี้ ในพารามิเตอร์ของเมธอด เราจะสร้างและป้อนข้อมูลออบเจ็กต์บัฟเฟอร์โปรโตคอลคำขอ (ในกรณีนี้คือ Point
) หากการเรียกไม่แสดงข้อผิดพลาด เราจะอ่านข้อมูลการตอบกลับจากเซิร์ฟเวอร์ได้จากค่าที่ส่งคืนแรก
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>> {
//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!("*** SIMPLE RPC ***");
let point = proto!(Point{
latitude: 409_146_138,
longitude: -746_188_906
});
let response = client
.get_feature(Request::new(point))
.await?.into_inner();
println!("Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude());
Ok(())
}
7. ลองเลย
ก่อนอื่น หากต้องการเรียกใช้ไคลเอ็นต์และเซิร์ฟเวอร์ ให้เพิ่มเป็นเป้าหมายไบนารีลงใน Crate เราจึงต้องแก้ไขCargo.toml
ตามนั้นและเพิ่มข้อมูลต่อไปนี้
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
จากนั้นเรียกใช้คำสั่งต่อไปนี้จากไดเรกทอรีที่ทำงานอยู่
- เรียกใช้เซิร์ฟเวอร์ในเทอร์มินัลหนึ่ง
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server
- เรียกใช้ไคลเอ็นต์จากเทอร์มินัลอื่นโดยใช้คำสั่งต่อไปนี้
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-client
คุณจะเห็นเอาต์พุตลักษณะนี้ โดยระบบจะละเว้นการประทับเวลาเพื่อความชัดเจน
*** SIMPLE RPC *** FEATURE: Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Lat = 409146138, Lon = -746188906
8. ขั้นตอนถัดไป
- ดูวิธีการทำงานของ gRPC ในข้อมูลเบื้องต้นเกี่ยวกับ gRPC และแนวคิดหลัก
- ดูบทแนะนำเกี่ยวกับพื้นฐาน