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 должны согласовать две вещи:
- Аргументы приняты
- Возвращаемое значение
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-серверами, а результаты собираются клиентом для дальнейшей обработки. Он обеспечивает эффективное совместное использование вычислительных ресурсов и позволяет создавать распределенные приложения.