Go gRPC ping barato


golang

Introducción

En este artículo exploraremos gRPC con una aplicación sencilla de ping, básicamente realizaremos un ping y mediremos el tiempo que toma para que el mensaje vaya al servidor y regrese antes de reportarlo en la terminal. Puedes encontrar el código fuente aquí.


Protobuf

Como probablemente ya sepas, gRPC serializa datos utilizando protocol buffers. Vamos a crear un RPC Unario de la siguiente manera.

syntax = "proto3";

service PingService {
  rpc Ping (PingRequest) returns (PingResponse);
}

message PingRequest {
  string data = 1;
}

message PingResponse {
  string data = 1;
}

Con este archivo estamos definiendo un servicio que será capaz de enviar una única PingRequest y obtener una única PingResponse. Tenemos un campo Data que va y viene para enviar algunos bytes por la red (aunque no nos importe mucho, podría ser importante o crucial en una prueba de rendimiento).


Generando el código

Para poder utilizar protobuf necesitamos generar el código para la aplicación que estamos escribiendo, en este caso para golang, el comando sería el siguiente:

 protoc -I ping/ ping/ping.proto --go_out=plugins=grpc:ping

Esto nos dará una definición del servicio y las estructuras necesarias para manejar los datos que hemos definido como mensajes.


Cliente

El cliente realiza la mayor parte del trabajo aquí. Como puedes ver, puedes suministrar 2 argumentos: uno para apuntar a otro host:puerto y el segundo para enviar una cadena de texto a tu gusto. Luego mide el tiempo que tarda en enviar y recibir el mensaje de vuelta, y lo imprime en la pantalla con una línea similar a la del comando ping real en Linux.

package main

import (
    "context"
    "log"
    "os"
    "time"

    pb "github.com/kainlite/grpc-ping/ping"
    "google.golang.org/grpc"
)

const (
    defaultAddress = "localhost:50000"
    defaultData    = "00"
)

func main() {
    data := defaultData
    address := defaultAddress
    if len(os.Args) > 2 {
        address = os.Args[1]
        data = os.Args[2]
    }

    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewPingServiceClient(conn)

    index := 0
    for {
        trip_time := time.Now()
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        r, err := c.Ping(ctx, &pb.PingRequest{Data: data})
        if err != nil {
            log.Fatalf("could not connect to: %v", err)
        }

        log.Printf("%d characters roundtrip to (%s): seq=%d time=%s", len(r.Data), address, index, time.Since(trip_time))
        time.Sleep(1 * time.Second)
        index++
    }
}

Servidor

El servidor es simplemente un servidor de eco, ya que enviará de vuelta cualquier cosa que le envíes y lo registrará en la consola. De manera predeterminada, escuchará en el puerto 50000.

package main

import (
    "context"
    "log"
    "net"

    pb "github.com/kainlite/grpc-ping/ping"
    "google.golang.org/grpc"
)

const (
    port = ":50000"
)

// server is used to implement ping.PingServer.
type server struct{}

// Ping implements ping.PingServer
func (s *server) Ping(ctx context.Context, in *pb.PingRequest) (*pb.PingResponse, error) {
    log.Printf("Received: %v", in.Data)
    return &pb.PingResponse{Data: "Data: " + in.Data}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterPingServiceServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

Probándolo
Ping regular
$ ping localhost -c 4
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.145 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.152 ms
64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.154 ms
64 bytes from localhost (::1): icmp_seq=4 ttl=64 time=0.141 ms

--- localhost ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3144ms
rtt min/avg/max/mdev = 0.141/0.148/0.154/0.005 ms

Cliente

Esto es lo que veríamos en la terminal mientras lo probamos.

$ go run ping_client/main.go                
2019/06/23 18:01:02 8 characters roundtrip to (localhost:50000): seq=0 time=1.941841ms
2019/06/23 18:01:03 8 characters roundtrip to (localhost:50000): seq=1 time=420.992µs
2019/06/23 18:01:04 8 characters roundtrip to (localhost:50000): seq=2 time=401.115µs
2019/06/23 18:01:05 8 characters roundtrip to (localhost:50000): seq=3 time=428.467µs
2019/06/23 18:01:06 8 characters roundtrip to (localhost:50000): seq=4 time=374.057µs

Como puedes ver, la conexión inicial toma un poco más de tiempo, pero después de eso el tiempo de ida y vuelta es muy consistente (por supuesto, nuestro simple ping no cubre errores, pérdida de paquetes, etc.).


Servidor

El servidor solo hace eco de lo que recibe y lo registra en la consola.

$ go run ping_server/main.go                       
2019/06/23 18:01:02 Received: 00
2019/06/23 18:01:03 Received: 00
2019/06/23 18:01:04 Received: 00
2019/06/23 18:01:05 Received: 00
2019/06/23 18:01:06 Received: 00

Notas finales

Como puedes ver, gRPC es bastante rápido y simplifica mucho todo lo que necesitas hacer para tener un sistema de mensajería altamente eficiente o una comunicación entre microservicios, por ejemplo. También es fácil generar el código base para cualquier lenguaje que prefieras y tener una interfaz común que todos deben aceptar.


Errata

Si encuentras algún error o tienes alguna sugerencia, por favor envíame un mensaje para que se corrija.



No tienes cuenta? Regístrate aqui

Ya registrado? Iniciar sesión a tu cuenta ahora.

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido