1. Введение
В этом практическом занятии вы будете использовать gRPC-Rust для создания клиента и сервера, которые составляют основу приложения для сопоставления маршрутов, написанного на Rust.
К концу этого урока у вас будет клиент, который подключается к удалённому серверу, используя официальную реализацию протокола gRPC на Rust, чтобы получить название или почтовый адрес местоположения по определённым координатам на карте. Полноценное приложение может использовать эту клиент-серверную архитектуру для перечисления или обобщения точек интереса вдоль маршрута.
Сервис определяется в файле Protocol Buffers, который будет использоваться для генерации шаблонного кода для клиента и сервера, чтобы они могли взаимодействовать друг с другом, экономя ваше время и усилия на реализации этой функциональности.
Сгенерированный код учитывает не только сложности взаимодействия между сервером и клиентом, но и сериализацию и десериализацию данных.
Что вы узнаете
- Как использовать Protocol Buffers для определения API сервиса.
- Как создать gRPC-клиент на основе определения Protocol Buffer с помощью автоматической генерации кода.
- Понимание взаимодействия клиент-сервер с использованием gRPC.
Данный практический семинар предназначен для разработчиков на Rust, которые только начинают работать с gRPC или хотят освежить свои знания gRPC, а также для всех, кто заинтересован в создании распределенных систем. Предварительный опыт работы с gRPC не требуется.
2. Прежде чем начать
Предварительные требования
Убедитесь, что у вас установлены следующие компоненты:
- GCC. Следуйте инструкциям здесь .
- Инструкции по установке Git здесь .
- Rust , версия 1.88.0. Следуйте инструкциям по установке здесь .
Получите код
Чтобы вам не пришлось начинать с нуля, в этом практическом руководстве представлен шаблон исходного кода приложения, который вы сможете доработать. Следующие шаги покажут вам, как завершить приложение, включая использование плагинов компилятора 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/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 обычно называются protobufs. Для получения дополнительной информации о терминологии gRPC см. раздел «Основные концепции, архитектура и жизненный цикл gRPC».
Метод обслуживания
Сначала определим методы нашего сервиса, а затем определим типы сообщений Point и Feature . В файле proto/routeguide.proto содержится структура service с именем RouteGuide , которая определяет один или несколько методов, предоставляемых сервисом приложения.
Добавьте 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-метод: простой RPC-вызов , при котором клиент отправляет запрос на сервер и ожидает ответа, подобно вызову локальной функции.
Типы сообщений
В файле proto/routeguide.proto исходного кода сначала определите тип сообщения Point . Point представляет собой пару координат широты и долготы на карте. Для этого практического задания используйте целые числа для координат:
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
Класс 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() , шаг за шагом:
- Укажите порт, который мы хотим использовать для приема запросов от клиентов.
- Создайте объект
RouteGuideServiceс загруженными функциями, вызвав вспомогательную функциюload() - Создайте экземпляр gRPC-сервера, используя
RouteGuideServer::new()и созданный нами сервис. - Зарегистрируйте реализацию нашего сервиса на gRPC-сервере.
- Вызовите
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() связывает универсальный канал с сгенерированным клиентским заглушкой, реализующим методы, определенные в нашем определении сервиса .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) . Ожидание возвращенного future позволяет получить ответ 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. Попробуйте.
Для запуска клиента и сервера сначала убедитесь, что обе целевые бинарные задачи определены в Cargo.toml :
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Затем выполните следующие команды из нашей рабочей директории:
- Запустите сервер в одном терминале:
cargo run --bin routeguide-server
- Запустите клиент из другого терминала:
cargo run --bin routeguide-client
В результате вы увидите примерно такой вывод, при этом временные метки для наглядности опущены:
*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906
8. Что дальше?
- Перейдите к практическому занятию по gRPC-Rust (стриминг) .
- Изучите официальный репозиторий gRPC-Rust .
- Подробнее об архитектуре gRPC можно узнать в разделе «Основные концепции» .
- Ознакомиться с документацией gRPC-Rust можно на сайте gRPC.io.
9. Участники этого семинара по программированию
- Кэти Чжао
- Лучио Франко
- Арвин Брайт
- Натаниэль Форд