1. Introducción
En este codelab, usarás gRPC-Rust para crear un cliente y un servidor que formen la base de una aplicación de asignación de rutas escrita en Rust.
Al final del instructivo, tendrás un cliente que se conectará a un servidor remoto con la implementación oficial de Rust del protocolo gRPC para recuperar el nombre o la dirección postal de una ubicación en coordenadas específicas de un mapa. Una aplicación completa podría usar este diseño de cliente-servidor para enumerar o resumir puntos de interés a lo largo de una ruta.
El servicio se define en un archivo de búferes de protocolo, que se usará para generar código estándar para el cliente y el servidor, de modo que puedan comunicarse entre sí, lo que te ahorrará tiempo y esfuerzo en la implementación de esa funcionalidad.
Este código generado se ocupa no solo de las complejidades de la comunicación entre el servidor y el cliente, sino también de la serialización y deserialización de datos.
Qué aprenderás
- Cómo usar búferes de protocolo para definir una API de servicio
- Cómo compilar un cliente basado en gRPC a partir de una definición de búfer de protocolo con la generación de código automatizada
- Una comprensión de la comunicación entre cliente y servidor con gRPC
Este codelab está dirigido a desarrolladores de Rust que no conocen gRPC o que desean repasar gRPC, o cualquier otra persona interesada en compilar sistemas distribuidos. No se requiere experiencia previa con gRPC.
2. Antes de comenzar
Requisitos previos
Asegúrate de haber instalado lo siguiente:
- GCC. Sigue las instrucciones que aparecen aquí.
- Git: instrucciones de instalación aquí.
- Rust, versión 1.88.0. Sigue las instrucciones de instalación que aparecen aquí.
Obtén el código
Para que no tengas que comenzar desde cero, este codelab proporciona un andamio del código fuente de la aplicación para que lo completes. En los siguientes pasos, se mostrará cómo finalizar la aplicación, incluido el uso de los complementos del compilador de búferes de protocolo para generar el código gRPC estándar.
Primero, crea el directorio de trabajo del codelab y cd en él:
mkdir grpc-rust-getting-started && cd grpc-rust-getting-started
Descarga y extrae el 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
Como alternativa, puedes descargar el archivo .zip que contiene solo el directorio del codelab y descomprimirlo de forma manual.
El código fuente completo está disponible en GitHub si deseas omitir la escritura de una implementación.
3. Define el servicio
El primer paso es definir el servicio gRPC de la aplicación, su método RPC y sus tipos de mensajes de solicitud y respuesta con búferes de protocolo. Tu servicio proporcionará lo siguiente:
- Un método RPC llamado
GetFeatureque el servidor implementa y el cliente llama - Los tipos de mensajes
PointyFeatureque son estructuras de datos intercambiadas entre el cliente y el servidor cuando se usa el métodoGetFeatureEl cliente proporciona coordenadas de mapa como unPointen su solicitudGetFeatureal servidor, y el servidor responde con unaFeaturecorrespondiente que describe lo que se encuentra en esas coordenadas.
Este método RPC y sus tipos de mensajes se definirán en el archivo proto/routeguide.proto del código fuente proporcionado.
Los búferes de protocolo se conocen comúnmente como protobufs. Para obtener más información sobre la terminología de gRPC, consulta Conceptos básicos, arquitectura y ciclo de vida de gRPC.
Método de servicio
Primero, definamos nuestros métodos de servicio y, luego, definamos nuestros tipos de mensajes Point y Feature. El archivo proto/routeguide.proto tiene una estructura service llamada RouteGuide que define uno o más métodos proporcionados por el servicio de la aplicación.
Agrega el método rpc GetFeature dentro de la definición RouteGuide. Como se explicó anteriormente, este método buscará el nombre o la dirección de una ubicación a partir de un conjunto de coordenadas determinado, por lo que GetFeature mostrará una Feature para un Point determinado:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Este es un método RPC unario: una RPC simple en la que el cliente envía una solicitud al servidor y espera a que vuelva una respuesta, al igual que una llamada a función local.
Tipos de mensajes
En el archivo proto/routeguide.proto del código fuente, primero define el tipo de mensaje Point. Un Point representa un par de coordenadas de latitud y longitud en un mapa. Para este codelab, usa números enteros para las coordenadas:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Los números 1 y 2 son números de ID únicos para cada uno de los campos de la estructura message.
A continuación, define el tipo de mensaje Feature. Una Feature usa un campo string para el nombre o la dirección postal de algo en una ubicación especificada por un Point:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
4. Genera el código del cliente y del servidor
Ya te proporcionamos el código generado del archivo .proto en el directorio generated/, incluidas todas las adiciones que realizaste anteriormente. Sin embargo, nos gustaría tomar un momento para explicar cómo funciona la generación de código.
Nuestro archivo .proto describe todas las estructuras y funciones que usa un cliente o servidor. Usamos una secuencia de comandos de compilación de Cargo (build.rs) junto con el crate grpc-protobuf-build para generar este código automáticamente.
En Cargo.toml, agregamos grpc-protobuf-build en [build-dependencies].
En build.rs, configuramos grpc_protobuf_build::CodeGen para compilar proto/routeguide.proto en el directorio generated/. Las líneas clave son las siguientes:
grpc_protobuf_build::CodeGen::new()
.include("proto")
.input("routeguide.proto")
.output_dir("generated")
.compile()
.unwrap();
Esto llama a la generación de código del crate grpc_protobuf_build y le pasa el routeguide.proto. Lo incluimos en un código para que solo se ejecute cuando se pasa una marca de función, de modo que solo se vuelva a generar cuando lo desees. No tienes que ejecutarlo ahora, ya que ya generamos el código por ti.
cargo build --bin routeguide-server --features regenerate_proto
Cuando ejecutas la compilación de Cargo, build.rs compila las definiciones de búferes de protocolo en el directorio generate/, incluidos los siguientes elementos:
- Definiciones de estructuras para los tipos de mensajes
PointyFeature - Un rasgo de servicio de Tonic que deberemos implementar para el servidor:
route_guide_server::RouteGuide - Un tipo de cliente de gRPC-Rust que usaremos para llamar al servidor:
route_guide_client::RouteGuideClient<T>
Puedes consultar la guía de protoc-gen-rust-grpc para obtener más información.
A continuación, implementaremos los métodos de servicio en el servidor.
5. Implementa el servicio
En src/server/server.rs, podemos incluir el código generado en el alcance a través de la macro include_generated_proto! de gRPC y, luego, importar el rasgo RouteGuide y Point.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature,
};
Podemos comenzar definiendo una estructura para representar nuestro servicio. Por ahora, podemos hacerlo en src/server/server.rs:
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
Ahora, debemos implementar el rasgo route_guide_server::RouteGuide de nuestro código generado.
RPC unaria simple
RouteGuideService implementa todos nuestros métodos de servicio. La función get_feature en el servidor es donde se realiza el trabajo principal: toma un mensaje Point del cliente y muestra en un mensaje Feature la información de ubicación correspondiente de una lista de lugares conocidos. Esta es la implementación de la función en 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()))
}
}
Una vez que implementamos este método, también debemos iniciar un servidor gRPC para que los clientes puedan usar nuestro servicio. Reemplaza main() por lo siguiente.
#[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(())
}
Esto es lo que sucede en main(), paso a paso:
- Especifica el puerto que queremos usar para escuchar las solicitudes del cliente.
- Crea un
RouteGuideServicecon funciones cargadas llamando a la función auxiliarload(). - Crea una instancia del servidor gRPC con
RouteGuideServer::new()usando el servicio que creamos. - Registra nuestra implementación de servicio con el servidor gRPC.
- Llama a
serve()en el servidor con los detalles de nuestro puerto para realizar una espera de bloqueo hasta que se finalice el proceso.
6. Crea el cliente
En esta sección, veremos cómo crear un cliente de Rust para nuestro servicio RouteGuide en src/client/client.rs.
Al igual que en src/server/server.rs, podemos incluir el código generado en el alcance a través de la macro include_generated_proto! de gRPC y, luego, importar el tipo RouteGuideClient.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_client::RouteGuideClient,
Point,
};
Llama a métodos de servicio
En gRPC-Rust, las RPC son asíncronas y no bloquean, y usan la sintaxis async/await de Rust para esperar respuestas del servidor.
Para llamar a los métodos de servicio, primero creamos un Channel con Channel::builder(), especificando la dirección del servidor (dns:///[::1]:10000) y las credenciales de conexión (LocalChannelCredentials). Luego, pasamos el canal a RouteGuideClient::new() para crear una instancia de nuestro cliente:
#[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);
}
En esta función, RouteGuideClient::new() vincula el canal genérico al stub de cliente generado que implementa los métodos definidos en nuestra definición de servicio .proto.
RPC simple
Llamar a la RPC simple GetFeature es tan sencillo como llamar a un método local. Agrega esto en 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");
En gRPC-Rust, pasamos el mensaje protobuf Point directamente a client.get_feature(point). Esperar el futuro que se muestra produce la respuesta Feature directamente, sin necesidad de más llamadas al método.
A continuación, imprime los campos de la respuesta:
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
En total, la función main() del cliente debería verse de la siguiente manera:
#[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. Probar
Para ejecutar tu cliente y servidor, primero verifica que ambos destinos binarios estén definidos en Cargo.toml:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Luego, ejecuta los siguientes comandos desde nuestro directorio de trabajo:
- Ejecuta el servidor en una terminal:
cargo run --bin routeguide-server
- Ejecuta el cliente desde otra terminal:
cargo run --bin routeguide-client
Verás un resultado como este, con marcas de tiempo omitidas para mayor claridad:
*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906
8. ¿Qué sigue?
- Continúa con el codelab Cómo comenzar a usar gRPC-Rust (transmisión).
- Explora el repositorio oficial de gRPC-Rust.
- Obtén más información sobre la arquitectura de gRPC en Conceptos básicos.
- Consulta la documentación de gRPC-Rust en gRPC.io.
9. Colaboradores de este codelab
- Cathy Zhao
- Lucio Franco
- Arvind Bright
- Nathaniel Ford