RPC расшифровывается как «Удаленный вызов процедуры». Это способ для компьютерной программы запросить услугу у программы, расположенной на другом компьютере в сети, без необходимости разбираться в деталях сети.

Запрос делается в форме вызова процедуры, как если бы служба была частью вызывающей программы. RPC используется для связи между программами и является важной функцией многих распределенных систем.

В системе RPC на сервер отправляется запрос на выполнение указанной процедуры с набором параметров. Сервер выполняет процедуру и возвращает результат клиенту.

Это позволяет клиентской программе запрашивать услуги у серверной программы, расположенной на другом компьютере, без необходимости разбираться в сетевых деталях.

В Go пакет net/rpc обеспечивает поддержку связи RPC. Пакет позволяет программе экспортировать объект и сделать его доступным для удаленного доступа, а также вызывать методы для удаленного объекта.

Чтобы использовать пакет net/rpc, тип должен быть определен как реализация методов, которые будут вызываться удаленно. Этот тип называется типом «сервер». Тип сервера должен иметь метод со следующей сигнатурой:

type MyType int

func (t *MyType) MyMethod(args *MyArgs, reply *MyReply) error

→ Параметр «args» — это указатель на значение типа, содержащего аргументы для вызова.

→ Параметр «reply» — это указатель на значение типа, которое будет содержать результат вызова.

Метод должен возвращать значение ошибки, если во время выполнения метода произошла ошибка.

После определения типа сервера его можно зарегистрировать в системе RPC с помощью функции «rpc.Register». Это позволяет удаленно вызывать методы типа сервера.

Затем клиент может использовать функцию «rpc.Dial», чтобы подключиться к серверу и вызвать методы для типа сервера. Функция rpc.Call используется для вызова метода на сервере, а функция rpc.Go может использоваться для асинхронного вызова метода.

Создание RPC-сервера

Давайте создадим простой сервер RPC, который отправляет время сервера обратно клиенту RPC. Во-первых, мы начинаем с сервера.

Сервер RPC и клиент RPC должны согласовать две вещи:

  1. Аргументы приняты
  2. Возвращаемое значение
package main

import (
   "fmt"
   "log"
   "net"
   "net/http"
   "net/rpc"
   "time"
)

type Request struct{}

type Server struct{}

func (s *Server) GetTime(req *Request, res *int64) error {
     *res = time.Now().UTC().Unix()
     return nil
}

func main() {
     
     server := new(Server)
     
     rpc.Register(server)
     
     rpc.HandleHTTP()
    
     listener, err := net.Listen("tcp", ":1234")

     if err != nil {
        log.Fatal("listen error:", err)
     }
     
     fmt.Println("Listening:")
     http.Serve(listener, nil)
}

Расшифровка кода:

package main

import (
 "fmt"
 "log"
 "net"
 "net/http"
 "net/rpc"
 "time"
)

Это импорт пакетов для кода. Пакеты fmt, log, net, net/http и net/rpc необходимы для различных задач, таких как форматирование вывода, регистрация сообщений, создание сетевых подключений, обработка HTTP-запросов и реализация RPC. Пакет time используется для получения текущего времени.

type Request struct{}

Это пустая структура, которая используется в качестве типа аргумента для метода GetTime RPC.

type Server struct{}

Это определение структуры Server. Он используется для реализации метода GetTime RPC.

func (s *Server) GetTime(req *Request, res *int64) error {
 // Fill res pointer to send the data back
 *res = time.Now().UTC().Unix()
 return nil
}

Это реализация метода GetTime RPC. В качестве аргументов он принимает указатель на структуру Request и указатель на структуру int64. Метод устанавливает значение указателя res на текущую отметку времени Unix в часовом поясе UTC, используя выражение time.Now().UTC().Unix(). Затем он возвращает nil, чтобы указать, что метод выполнен успешно.

func main() {

 server := new(Server)

 rpc.Register(server)

 rpc.HandleHTTP()

 listener, err := net.Listen("tcp", ":1234")
 if err != nil {
  log.Fatal("listen error:", err)
 }

 fmt.Println("Listening:")
 http.Serve(listener, nil)
}

Это основная функция программы. Он создает новый экземпляр структуры Server, регистрирует его как сервер RPC, создает обработчик HTTP для сервера RPC, прослушивает входящие соединения через порт 1234 и запускает сервер HTTP для обработки запросов RPC.

Создание RPC-клиента

package main

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

type Args struct{}

type Time struct{}

func main() {

     client, err := rpc.DialHTTP("tcp", "localhost:1234")
    
     if err != nil {
        log.Fatal("dialing:", err)
     }
    
     args := &Args{}
     
     var reply int64
    
     err = client.Call("Server.GetTime", args, &reply)
    
     if err != nil {
        log.Fatal("arith error:", err)
     }
     serverTime := time.Unix(reply, 0)
     
     fmt.Printf("Time: %v\n", serverTime.Format(time.RFC1123))
}

Разбивка кода:

type Args struct{}

Это пустая структура, которая используется в качестве типа аргумента для метода GetTime RPC.

type Time struct{}

Это пустая структура, которая используется в качестве типа ответа для метода GetTime RPC.

client, err := rpc.DialHTTP("tcp", "localhost:1234")

   if err != nil {
    log.Fatal("dialing:", err)
   }

Этот код подключается к RPC-серверу по указанному адресу (в данном случае «localhost:1234»). Если при подключении к серверу возникает ошибка, он регистрирует сообщение об ошибке и выходит из программы.

args := &Args{}

Этот код создает экземпляр структуры Args, который используется в качестве аргумента для метода GetTime RPC.

 var reply int64

Этот код объявляет переменную типа int64 с именем reply, которая будет использоваться для хранения ответа от сервера RPC.

 err = client.Call("Server.GetTime", args, &reply)
 if err != nil {
  log.Fatal("arith error:", err)
 }

Этот код вызывает метод RPC GetTime на сервере и передает ему аргумент args. Ответ метода хранится в переменной reply. Если при вызове метода возникает ошибка, он регистрирует сообщение об ошибке и завершает работу программы.

 serverTime := time.Unix(reply, 0)

 fmt.Printf("Time: %v\n", serverTime.Format(time.RFC1123))

Этот код преобразует временную метку Unix, полученную от сервера, в значение time.Time с помощью функции time.Unix. Затем он присваивает полученное значение переменной serverTime и форматирует ее так, чтобы сделать ее удобочитаемой.

Выход:

Консоль сервера

Nithin@NITHIN-KUMAR MINGW64 ~/go/src/github.com/nithin/RPC
$ go run BasicRPCServer.go
Listening:

Консоль клиента

Nithin@NITHIN-KUMAR MINGW64 ~/go/src/github.com/nithin/RPC
$ go run BasicPRCClient.go
Time: Thu, 05 Jan 2023 19:35:38 IST

Заключение:

Обратите внимание на прелесть использования удаленных вызовов процедур (RPC) в том, что клиент и сервер могут находиться на разных компьютерах и при этом взаимодействовать друг с другом. В этом примере клиент работает как отдельная программа, а сервер предоставляет текущее время. Это фундаментальная концепция распределенных систем, где задачи распределяются между несколькими RPC-серверами, а результаты собираются клиентом для дальнейшей обработки. Он обеспечивает эффективное совместное использование вычислительных ресурсов и позволяет создавать распределенные приложения.