Skip to content

Latest commit

 

History

History
400 lines (316 loc) · 10.2 KB

File metadata and controls

400 lines (316 loc) · 10.2 KB

8.4 RPC

En secciones anteriores hablamos sobre como escribir aplicaciones de red basadas en Sockets y HTTP. Aprendimos que ambos usan un modelo de "intercambio de información", en donde los clientes envían peticiones y los servidores las responden. Este tipo de intercambio está basado en un formato específico que ambos lados conocen y saben como comunicarse con el. Sin embargo, existen muchas aplicaciones independientes que no usan este modelo, pero en vez de eso llaman servicios como si llamaran funciones normales.

RPC fue creado para ser el modo de llamado de funciones para sistemas de red. Los clientes ejecutan RPC como si fueran funciones nativas. excepto que empaquetan la función y los parámetros y los envían a través de la red a un servidor. El servidor puede desempaquetar los parámetros y procesar la petición, ejecutando los resultados de vuelta hacia el cliente.

En ciencias de la computación, un llamado de procedimiento remoto (Remote Call Procedure RPC) es un tipo de comunicación inter proceso que permite a un programa de computador ejecutar una subrutina o precedimiento en otro espacio de direcciones (usualmente otro computador en un red compartida) sin que el programador específicamente codifique los detalles para esta interacción remota. Esto es, el programador escribe el mismo código ya sea que la subrutina se ejecute local o remotamente. Cuando el software en cuestión usa principios orientados a objetos, el RPC es llamado invocación remota o invocación remota de métodos.

Principio de trabajo de RPC

Figura 8.8 Principio de trabajo de RPC

Normalmente, un llamado RPC de un cliente a un servidor tiene diez pasos:

    1. Llamar al manejador del cliente que ejecute la transmisieon de los argumentos.
    1. Llamar al kernel del sistema para enviar mensajes de red.
    1. Enviar mensajes a los hosts remotos.
    1. El servidor recibe y maneja los argumentos.
    1. Se ejecuta el proceso remoto.
    1. Se retorna el resultado de la ejecución al manejador.
    1. El servidor maneja la llamada a un kernel remoto.
    1. El mensaje es enviado al kernel local.
    1. El cliente maneja el mensaje del kernel.
    1. El cliente obtiene los resultados del manejador correspondiente.

RPC en Go

Go tiene soporte oficial para RPC en su librería estándar en tres niveles, que son TCP, HTTP y JSON. Nota que Go RPC no es como otro sistema tradicional RPC. Requiere que uses aplicaciones Go en cliente y servidor porque codifica el contenido usando Gob.

Funciones que Go RPC deben seguir las siguientes reglara para tener acceso remoto, de otra manera, las llamadas serán ignoradas.

  • Las funciones deben ser exportadas (Primera letra en mayúscula)
  • Las funciones deben tener dos argumentos con tipos exportados.
  • El primer argumento es para recibir del cliente y el segundo debe ser un puntero y debe ser para responderle al cliente.
  • Las funciones deben tener valor de reporte o un tipo de error.

Por ejemplo:

	func (t *T) MethodName(argType T1, replyType *T2) error

Donde T, T1 y T2 deben estar codificados en el paquete package/gob.

Cualquier tipo de RPC tiene que pasar por la red para transferir datos. Go RPC puede usar HTTP o TCO. Los beneficios de usar HTTP es que puedes reusar algunas funciones del paquete net/http.

HTTP RPC

Lado del servidor HTTP:

	package main

	import (
		"errors"
		"fmt"
		"net/http"
		"net/rpc"
	)

	type Args struct {
		A, B int
	}

	type Quotient struct {
		Quo, Rem int
	}

	type Arith int

	func (t *Arith) Multiply(args *Args, reply *int) error {
		*reply = args.A * args.B
		return nil
	}

	func (t *Arith) Divide(args *Args, quo *Quotient) error {
		if args.B == 0 {
			return errors.New("divide by zero")
		}
		quo.Quo = args.A / args.B
		quo.Rem = args.A % args.B
		return nil
	}

	func main() {

		arith := new(Arith)
		rpc.Register(arith)
		rpc.HandleHTTP()

		err := http.ListenAndServe(":1234", nil)
		if err != nil {
			fmt.Println(err.Error())
		}
	}

Registramos un servicio RPC de Arith, luego registramos este servicio a través de rpc.HandleHTTP. Después de eso, somos capaces de transferir datos a través de HTTP.

Código del lado del cliente:

	package main

	import (
		"fmt"
		"log"
		"net/rpc"
		"os"
	)

	type Args struct {
		A, B int
	}

	type Quotient struct {
		Quo, Rem int
	}


	func main() {
		if len(os.Args) != 2 {
			fmt.Println("Usage: ", os.Args[0], "server")
			os.Exit(1)
		}
		serverAddress := os.Args[1]

		client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
		if err != nil {
			log.Fatal("dialing:", err)
		}
		// Sincronizamos la llamada
		args := Args{17, 8}
		var reply int
		err = client.Call("Arith.Multiply", args, &reply)
		if err != nil {
			log.Fatal("arith error:", err)
		}
		fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)

		var quot Quotient
		err = client.Call("Arith.Divide", args, &quot)
		if err != nil {
			log.Fatal("arith error:", err)
		}
		fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)

	}

Compilamos el cliente y el servidor separadamente y luego iniciamos el servidor y el cliente. Entonces puedes tener algo similar después que ingreses algunos datos.

	$ ./http_c localhost
	Arith: 17*8=136
	Arith: 17/8=2 remainder 1

Como puedes ver, definimos una estructura para el tipo de retorno. La usamos como argumento de función en el lado del servidor, y como el tipo del segundo y tercer argumento del cliente client.Call. Esta llamada es muy importante. Tiene tres argumentos, donde el primero es el nombre de la función que va a ser llamado, el segundo es el argumento que quieres pasar y el último es el valor de retorno (de tipo puntero). Hasta aquí podemos ver que es fácil implementar RPC en Go.

TCP RPC

Vamos a probar el RPC que está basado en TCP, aquí esta el código del servidor:

	package main

	import (
		"errors"
		"fmt"
		"net"
		"net/rpc"
		"os"
	)

	type Args struct {
		A, B int
	}

	type Quotient struct {
		Quo, Rem int
	}

	type Arith int

	func (t *Arith) Multiply(args *Args, reply *int) error {
		*reply = args.A * args.B
		return nil
	}

	func (t *Arith) Divide(args *Args, quo *Quotient) error {
		if args.B == 0 {
			return errors.New("divide by zero")
		}
		quo.Quo = args.A / args.B
		quo.Rem = args.A % args.B
		return nil
	}

	func main() {

		arith := new(Arith)
		rpc.Register(arith)

		tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
		checkError(err)

		listener, err := net.ListenTCP("tcp", tcpAddr)
		checkError(err)

		for {
			conn, err := listener.Accept()
			if err != nil {
				continue
			}
			rpc.ServeConn(conn)
		}

	}

	func checkError(err error) {
		if err != nil {
			fmt.Println("Fatal error ", err.Error())
			os.Exit(1)
		}
	}

La diferencia entre HTTP RPC y TCP RPC es que tenemos que controlar las conexiones por nuestra propia cuenta si usamos TCP RPC, entonces pasamos las conexiones para el procesamiento RPC.

Como puedes adivinar, este puede ser un patrón que se bloquea. Eres libre de usar rutinas para esta aplicación en un experimento mas avanzado.

Código del lado del cliente:

	package main

	import (
		"fmt"
		"log"
		"net/rpc"
		"os"
	)

	type Args struct {
		A, B int
	}

	type Quotient struct {
		Quo, Rem int
	}

	func main() {
		if len(os.Args) != 2 {
			fmt.Println("Usage: ", os.Args[0], "server:port")
			os.Exit(1)
		}
		service := os.Args[1]

		client, err := rpc.Dial("tcp", service)
		if err != nil {
			log.Fatal("dialing:", err)
		}
		// Sincronizamos las llamadas
		args := Args{17, 8}
		var reply int
		err = client.Call("Arith.Multiply", args, &reply)
		if err != nil {
			log.Fatal("arith error:", err)
		}
		fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)

		var quot Quotient
		err = client.Call("Arith.Divide", args, &quot)
		if err != nil {
			log.Fatal("arith error:", err)
		}
		fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)

	}

La única diferencia en el lado del cliente es que los clientes HTTP usan DialHTTP donde los clientes TCP usan Dial(TCP).

JSON RPC

JSON RPC codifica la información en JSON en vez de Gob. Veamos un ejemplo de Go usando JSON RPC en el servidor.

	package main

	import (
		"errors"
		"fmt"
		"net"
		"net/rpc"
		"net/rpc/jsonrpc"
		"os"
	)

	type Args struct {
		A, B int
	}

	type Quotient struct {
		Quo, Rem int
	}

	type Arith int

	func (t *Arith) Multiply(args *Args, reply *int) error {
		*reply = args.A * args.B
		return nil
	}

	func (t *Arith) Divide(args *Args, quo *Quotient) error {
		if args.B == 0 {
			return errors.New("divide by zero")
		}
		quo.Quo = args.A / args.B
		quo.Rem = args.A % args.B
		return nil
	}

	func main() {

		arith := new(Arith)
		rpc.Register(arith)

		tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
		checkError(err)

		listener, err := net.ListenTCP("tcp", tcpAddr)
		checkError(err)

		for {
			conn, err := listener.Accept()
			if err != nil {
				continue
			}
			jsonrpc.ServeConn(conn)
		}

	}

	func checkError(err error) {
		if err != nil {
			fmt.Println("Fatal error ", err.Error())
			os.Exit(1)
		}
	}

JSON RPC está basado en TCP y no soporta HTTP todavía.

El código del cliente:

	package main

	import (
		"fmt"
		"log"
		"net/rpc/jsonrpc"
		"os"
	)

	type Args struct {
		A, B int
	}

	type Quotient struct {
		Quo, Rem int
	}

	func main() {
		if len(os.Args) != 2 {
			fmt.Println("Usage: ", os.Args[0], "server:port")
			log.Fatal(1)
		}
		service := os.Args[1]

		client, err := jsonrpc.Dial("tcp", service)
		if err != nil {
			log.Fatal("dialing:", err)
		}
		// Sincronizamos llamadas
		args := Args{17, 8}
		var reply int
		err = client.Call("Arith.Multiply", args, &reply)
		if err != nil {
			log.Fatal("arith error:", err)
		}
		fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)

		var quot Quotient
		err = client.Call("Arith.Divide", args, &quot)
		if err != nil {
			log.Fatal("arith error:", err)
		}
		fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)

	}

Resumen

Go tiene buen soporte para implementaciones de RPC sobre HTTP, TCP y JSON, que nos permite fácilmente desarrollar aplicaciones we distrubidas: sinembargo, es lamentable que Go no tenga soporte para SOAP RPC incluido, sin embargo algunas paquetes de terceros de código abierto ofrecen esto.

Enlaces