1. Introducción
En este codelab, usarás gRPC-Go para crear un cliente y un servidor que formen la base de una aplicación de asignación de rutas escrita en Go.
Al final del instructivo, tendrás un cliente que se conecta a un servidor remoto con gRPC para obtener el nombre o la dirección postal de lo que se encuentra en coordenadas específicas en un mapa. Una aplicación completa podría usar este diseño cliente-servidor para enumerar o resumir los puntos de interés a lo largo de una ruta.
El servicio se define en un archivo de Protocol Buffers, 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 encarga 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 los búferes de protocolo para definir una API de servicio
- Cómo compilar un cliente y un servidor basados en gRPC a partir de una definición de Protocol Buffers con la generación de código automatizada
- Conocimiento de la comunicación cliente-servidor con gRPC
Este codelab está dirigido a desarrolladores de Go que no conocen gRPC o que desean repasar sus conceptos, o a cualquier otra persona interesada en crear sistemas distribuidos. No se requiere experiencia previa con gRPC.
2. Antes de comenzar
Requisitos previos
Asegúrate de haber instalado lo siguiente:
- La versión 1.24.5 o posterior de la cadena de herramientas de Go Para obtener instrucciones de instalación, consulta la sección Comenzar de Go.
- El compilador de búferes de protocolo,
protoc
, versión 3.27.1 o posterior Para obtener instrucciones de instalación, consulta la guía de instalación del compilador. - Son los complementos del compilador de búferes de protocolo para Go y gRPC. Para instalar estos complementos, ejecuta los siguientes comandos:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Actualiza tu variable PATH
para que el compilador de búferes de protocolo pueda encontrar los complementos:
export PATH="$PATH:$(go env GOPATH)/bin"
Obtén el código
Para que no tengas que empezar desde cero, este codelab proporciona un andamio del código fuente de la aplicación para que lo completes. En los siguientes pasos, se muestra cómo finalizar la aplicación, incluido el uso de los complementos del compilador de búfer de protocolo para generar el código gRPC estándar.
Descarga este código fuente como un archivo .ZIP de GitHub y descomprime su contenido.
Como alternativa, 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 de gRPC de la aplicación, su método de RPC y sus tipos de mensajes de solicitud y respuesta con búferes de protocolo. Tu servicio proporcionará lo siguiente:
- Un método de RPC llamado
GetFeature
que el servidor implementa y el cliente llama. - Son los tipos de mensajes
Point
yFeature
que son estructuras de datos que se intercambian entre el cliente y el servidor cuando se usa el métodoGetFeature
. El cliente proporciona coordenadas de mapa como unPoint
en su solicitudGetFeature
al servidor, y el servidor responde con unFeature
correspondiente que describe lo que se encuentra en esas coordenadas.
Este método de RPC y sus tipos de mensajes se definirán en el archivo routeguide/route_guide.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 los conceptos básicos, la arquitectura y el ciclo de vida de gRPC.
Tipos de mensajes
En el archivo routeguide/route_guide.proto
del código fuente, primero define el tipo de mensaje Point
. Un objeto Point
representa un par de coordenadas de latitud y longitud en un mapa. En 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
. Un 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;
}
Método de servicio
El archivo route_guide.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 de 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
devolverá un 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 de 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.
4. Genera el código del cliente y del servidor
A continuación, genera el código gRPC estándar para el cliente y el servidor desde el archivo .proto
con el compilador de búfer de protocolo. En el directorio routeguide
, ejecuta lo siguiente:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ route_guide.proto
Este comando genera los siguientes archivos:
route_guide.pb.go
, que contiene funciones para crear los tipos de mensajes de la aplicación y acceder a sus datosroute_guide_grpc.pb.go
, que contiene funciones que el cliente usa para llamar al método gRPC remoto del servicio y funciones que el servidor usa para proporcionar ese servicio remoto.
A continuación, implementaremos el método GetFeature
en el servidor para que, cuando el cliente envíe una solicitud, el servidor pueda responder con una respuesta.
5. Implementa el servicio
La función GetFeature
del servidor es donde se realiza el trabajo principal: toma un mensaje Point
del cliente y devuelve en un mensaje Feature
la información de ubicación correspondiente de una lista de lugares conocidos. A continuación, se muestra la implementación de la función en server/server.go
:
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
for _, feature := range s.savedFeatures {
if proto.Equal(feature.Location, point) {
return feature, nil
}
}
// No feature was found, return an unnamed feature
return &pb.Feature{Location: point}, nil
}
Cuando se invoca este método después de una solicitud de un cliente remoto, se pasa a la función un objeto Context
que describe la llamada a RPC y un objeto de búfer de protocolo Point
de esa solicitud del cliente. La función devuelve un objeto de búfer de protocolo Feature
para la ubicación buscada y un error
según sea necesario.
En el método, completa un objeto Feature
con la información adecuada para el Point
determinado y, luego, return
lo junto con un error nil
para indicarle a gRPC que terminaste de controlar la RPC y que el objeto Feature
se puede devolver al cliente.
El método GetFeature
requiere que se cree y registre un objeto routeGuideServer
para que las solicitudes de los clientes para las búsquedas de ubicación se puedan enrutar a esa función. Esto se hace en main()
:
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
s := &routeGuideServer{}
s.loadFeatures()
pb.RegisterRouteGuideServer(grpcServer, s)
grpcServer.Serve(lis)
}
Esto es lo que sucede en main()
, paso a paso:
- Especifica el puerto TCP que se usará para escuchar las solicitudes de clientes remotos con
lis, err := net.Listen(...)
. De forma predeterminada, la aplicación usa el puerto TCP50051
, como se especifica en la variableport
o pasando el parámetro--port
en la línea de comandos cuando se ejecuta el servidor. Si no se puede abrir el puerto TCP, la aplicación finaliza con un error fatal. - Crea una instancia del servidor de gRPC con
grpc.NewServer(...)
y asígnale el nombregrpcServer
. - Crea un puntero a
routeGuideServer
, una estructura que representa el servicio de API de la aplicación, y nómbralos.
. - Usa
s.loadFeatures()
para completar el arrays.savedFeatures
con ubicaciones que se pueden buscar a través deGetFeature
. - Registra el servicio de API con el servidor de gRPC para que las llamadas a RPC a
GetFeature
se enruten a la función adecuada. - Llama a
Serve()
en el servidor con los detalles de nuestro puerto para realizar una espera de bloqueo de las solicitudes del cliente. Esto continúa hasta que se detiene el proceso o se llama aStop()
.
La función loadFeatures()
obtiene sus asignaciones de coordenadas a ubicación de server/testdata.go
.
6. Crea el cliente
Ahora, edita client/client.go
, que es donde implementarás el código del cliente.
Para llamar a los métodos del servicio remoto, primero debemos crear un canal de gRPC para comunicarnos con el servidor. Para ello, pasamos la cadena URI de destino del servidor (que, en este caso, es simplemente la dirección y el número de puerto) a grpc.NewClient()
en la función main()
del cliente de la siguiente manera:
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
La dirección del servidor, definida por la variable serverAddr
, es localhost:50051
de forma predeterminada y se puede anular con el parámetro --addr
en la línea de comandos cuando se ejecuta el cliente.
Si el cliente necesita conectarse a un servicio que requiere credenciales de autenticación, como credenciales de TLS o JWT, puede pasar un objeto DialOptions
como parámetro a grpc.NewClient
que contenga las credenciales requeridas. El servicio RouteGuide
no requiere credenciales.
Una vez que se configura el canal de gRPC, necesitamos un stub del cliente para realizar RPC a través de llamadas a funciones de Go. Obtenemos ese stub con el método NewRouteGuideClient
que proporciona el archivo route_guide_grpc.pb.go
generado a partir del archivo .proto
de la aplicación.
import (pb "github.com/grpc-ecosystem/codelabs/getting_started_unary/routeguide")
client := pb.NewRouteGuideClient(conn)
Llama a métodos de servicio
En gRPC-Go, las RPC operan en un modo de bloqueo o síncrono, lo que significa que la llamada a la RPC espera a que responda el servidor y mostrará una respuesta o un error.
RPC simple
Llamar a la RPC simple GetFeature
es casi tan sencillo como llamar a un método local, en este caso client.GetFeature
:
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
El cliente llama al método en el stub creado anteriormente. Para los parámetros del método, el cliente crea y completa un objeto de búfer de protocolo de solicitud Point
. También pasas un objeto context.Context
que nos permite cambiar el comportamiento de nuestra RPC si es necesario, como definir un límite de tiempo para la llamada o cancelar una RPC en curso. Si la llamada no devuelve un error, el cliente puede leer la información de respuesta del servidor desde el primer valor de devolución:
log.Println(feature)
En total, la función main()
del cliente debería verse de la siguiente manera:
func main() {
flag.Parse()
// Set up a connection to the gRPC server.
conn, err := grpc.NewClient("dns:///"+*serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
// Create a new RouteGuide stub.
client := pb.NewRouteGuideClient(conn)
point := &pb.Point{Latitude: 409146138, Longitude: -746188906}
log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
// Call GetFeature method on the client.
feature, err := client.GetFeature(context.TODO(), point)
if err != nil {
log.Fatalf("client.GetFeature failed: %v", err)
}
log.Println(feature)
}
7. Probar
Ejecuta los siguientes comandos en el directorio de trabajo de la aplicación para confirmar que el servidor y el cliente funcionen correctamente entre sí:
- Ejecuta el servidor en una terminal:
cd server go run .
- Ejecuta el cliente desde otra terminal:
cd client go run .
Verás un resultado como este, con marcas de tiempo omitidas para mayor claridad:
Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
8. ¿Qué sigue?
- Obtén información sobre cómo funciona gRPC en Introducción a gRPC y Conceptos básicos
- Lee el instructivo sobre conceptos básicos.
- Explora la referencia de la API.