What is the RPC

Remote Procedure Call (RPC for short) is a computer communication protocol. The protocol allows a program running on one computer to call a subroutine on another without the programmer having to program for this interaction. If the software involved uses object-oriented programming, remote procedure calls can also be called remote calls or remote method calls. Wikipedia: Remote procedure calls

In plain English, RPC allows you to call computer program methods across machines and languages. For example, I used go language to write a method getUserInfo to obtain user information, and deployed the GO program on ali Cloud server. Now I have a PHP project deployed on Tencent Cloud, and NEED to call the getUserInfo method of Golang to obtain user information. The process by which PHP calls the GO method across machines is called an RPC call.

How is RPC implemented in Golang

Implementing RPC in Golang is very simple, supported by packaged official libraries and some third-party libraries. Go RPC can use TCP or HTTP to transfer data and can use many types of codec for the data to be transferred. Golang net/ RPC library uses Encoding/GOB for encoding and decoding, and supports TCP or HTTP data transmission. Because other languages do not support GOB codec, RPC methods implemented using NET/RPC library cannot be called across languages.

Golang also provides the official NET/RPC/JSONRPC library to implement RPC methods. JSON RPC uses JSON to encode and decode data, thus supporting cross-language calls. However, the current JSONRPC library is based on TCP protocol and does not support data transmission through HTTP.

In addition to the RPC library officially provided by Golang, there are many third-party libraries to support the implementation of RPC in Golang. Most of the implementation of the third-party RPC libraries are using Protobuf to encode and decode data. According to the Protobuf declaration file, RPC method definition and service registration codes are automatically generated. It’s easy to make RPC service calls in Golang.

Net/RPC library

The following example demonstrates how to implement RPC methods using Golang’s official NET/RPC library, using HTTP as the RPC carrier and listening for client connection requests through the NET/HTTP package.

$GOPATH/src/test/rpc/rpc_server.go

package main



import (

"errors"

"fmt"

"log"

"net"

"net/http"

"net/rpc"

"os"

)



// The arithmetic operation structure

type Arith struct {

}



// Arithmetic operation request structure

type ArithRequest struct {

A int

B int

}



// Arithmetic operation response structure

type ArithResponse struct {

Pro int // product

Quo int / /

Rem int // remainder

}



// Multiply

func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {

res.Pro = req.A * req.B

return nil

}



// Divide

func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error {

if req.B == 0 {

return errors.New("divide by zero")

}

res.Quo = req.A / req.B

res.Rem = req.A % req.B

return nil

}



func main() {

Rpc.register (new(Arith)) // Register the RPC service

Rpc.handlehttp () // Uses HTTP as the RPC carrier



Lis, err := net.Listen(" TCP ", "127.0.0.1:8095")

if err ! = nil {

log.Fatalln("fatal error: ", err)

}



fmt.Fprintf(os.Stdout, "%s", "start connection")



http.Serve(lis, nil)

}

After the above server program runs, it will listen on the local port 8095. We can implement a client program to connect to the server and implement the RPC method call.

$GOPATH/src/test/rpc/rpc_client.go

package main



import (

"fmt"

"log"

"net/rpc"

)



// Arithmetic operation request structure

type ArithRequest struct {

A int

B int

}



// Arithmetic operation response structure

type ArithResponse struct {

Pro int // product

Quo int / /

Rem int // remainder

}



func main() {

Conn, err: = RPC. DialHTTP (" TCP ", "127.0.0.1:8095)"

if err ! = nil {

log.Fatalln("dailing error: ", err)

}



req := ArithRequest{9, 2}

var res ArithResponse



Err = conn.Call("Arith.Multiply", req, &res) //

if err ! = nil {

log.Fatalln("arith error: ", err)

}

fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)



err = conn.Call("Arith.Divide", req, &res)

if err ! = nil {

log.Fatalln("arith error: ", err)

}

fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)

}

Net/RPC/jsonrpc library

The above example demonstrates the implementation of RPC using NET/RPC, but there is no way to call the RPC methods implemented in the above example in other languages. So in the following example we will demonstrate the use of the NET/RPC/JsonRPC library to implement RPC methods that support cross-language invocation.

$GOPATH/src/test/rpc/jsonrpc_server.go

package main



import (

"errors"

"fmt"

"log"

"net"

"net/rpc"

"net/rpc/jsonrpc"

"os"

)



// The arithmetic operation structure

type Arith struct {

}



// Arithmetic operation request structure

type ArithRequest struct {

A int

B int

}



// Arithmetic operation response structure

type ArithResponse struct {

Pro int // product

Quo int / /

Rem int // remainder

}



// Multiply

func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {

res.Pro = req.A * req.B

return nil

}



// Divide

func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error {

if req.B == 0 {

return errors.New("divide by zero")

}

res.Quo = req.A / req.B

res.Rem = req.A % req.B

return nil

}



func main() {

Rpc.register (new(Arith)) // Register the RPC service



Lis, err := net.Listen(" TCP ", "127.0.0.1:8096")

if err ! = nil {

log.Fatalln("fatal error: ", err)

}



fmt.Fprintf(os.Stdout, "%s", "start connection")



for {

Conn, err := lis.accept () // Accept the client connection request

if err ! = nil {

continue

}



Go func(conn net.conn) {// Process client requests concurrently

fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")

jsonrpc.ServeConn(conn)

}(conn)

}

}

After the above server program is started, it will listen on the local port 8096 and process the TCP connection request of the client. We can use Golang to implement a client program that connects to the above server and makes RPC calls.

$GOPATH/src/test/rpc/jsonrpc_client.go

package main



import (

"fmt"

"log"

"net/rpc/jsonrpc"

)



// Arithmetic operation request structure

type ArithRequest struct {

A int

B int

}



// Arithmetic operation response structure

type ArithResponse struct {

Pro int // product

Quo int / /

Rem int // remainder

}



func main() {

Conn, err: = jsonrpc. Dial (" TCP ", "127.0.0.1:8096)"

if err ! = nil {

log.Fatalln("dailing error: ", err)

}



req := ArithRequest{9, 2}

var res ArithResponse



Err = conn.Call("Arith.Multiply", req, &res) //

if err ! = nil {

log.Fatalln("arith error: ", err)

}

fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)



err = conn.Call("Arith.Divide", req, &res)

if err ! = nil {

log.Fatalln("arith error: ", err)

}

fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)

}

Protorpc library

In order to achieve cross-language invocation, we should choose a cross-language data codec method when implementing RPC methods in Golang, such as JSON. The jSONRPC mentioned above can meet this requirement, but it also has some disadvantages, such as not supporting HTTP transmission and low data codec performance. Therefore, some third-party RPC libraries choose to use Protobuf to encode and decode data, and provide some automatic generation of service registration codes. The following example uses protobuf to define the RPC method and its request response parameters, and uses the third-party ProtorPC library to generate the RPC service registration code.

First, you need to install protobuf and protoc executable commands, see this article: A Quick start Guide for Protobuf

Then, we write a proto file that defines the RPC method to implement and its associated parameters.

$GOPATH/src/test/rpc/pb/arith.proto

syntax = "proto3";

package pb;



// The arithmetic operation request structure

message ArithRequest {

int32 a = 1;

int32 b = 2;

}



// The arithmetic operation response structure

message ArithResponse {

int32 pro = 1; / / product

int32 quo = 2; / /,

int32 rem = 3; / / remainder

}



/ / RPC method

service ArithService {

rpc multiply (ArithRequest) returns (ArithResponse); // Multiply

rpc divide (ArithRequest) returns (ArithResponse); // Divide

}

Next we need to generate the RPC service code from the arix.proto file defined above. To install protorpc library: go get github.com/chai2010/protorpc then use protoc tool to generate code: Protoc –go_out=plugin= protorPC =.arith. Proto protoc –go_out=plugin= protorPC =.arith. Proto protoc –go_out=plugin=.arith.

Based on the generated arix.pb. go code we will implement an RPC server

$GOPATH/src/test/rpc/protorpc_server.go

package main



import (

"errors"

"test/rpc/pb"

)



// The arithmetic operation structure

type Arith struct {

}



// Multiply

func (this *Arith) Multiply(req *pb.ArithRequest, res *pb.ArithResponse) error {

res.Pro = req.GetA() * req.GetB()

return nil

}



// Divide

func (this *Arith) Divide(req *pb.ArithRequest, res *pb.ArithResponse) error {

if req.GetB() == 0 {

return errors.New("divide by zero")

}

res.Quo = req.GetA() / req.GetB()

res.Rem = req.GetA() % req.GetB()

return nil

}



func main() {

Pb. ListenAndServeArithService (" TCP ", "127.0.0.1:8097", the new (Arith))

}

Run the program to listen on port 8097 and receive the TCP connection from the client.

Go based on Ariti.pb. go to achieve a client program.

$GOPATH/src/test/protorpc_client.go

package main



import (

"fmt"

"log"

"test/rpc/pb"

)



func main() {

Conn, err := pb.DialArithService(" TCP ", "127.0.0.1:8097")

if err ! = nil {

log.Fatalln("dailing error: ", err)

}

defer conn.Close()



req := &pb.ArithRequest{9, 2}



res, err := conn.Multiply(req)

if err ! = nil {

log.Fatalln("arith error: ", err)

}

fmt.Printf("%d * %d = %d\n", req.GetA(), req.GetB(), res.GetPro())



res, err = conn.Divide(req)

if err ! = nil {

log.Fatalln("arith error ", err)

}

fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)

}

How do I call Golang’s RPC methods across languages

In the above three examples, we respectively use NET/RPC, NET/RPC/JSONRPC, ProtorPC to implement the RPC server in Golang, and give the corresponding GOLang client RPC call example, because JSON and Protobuf are multilingual support. So RPC methods implemented by JsonRPC and ProtorPC can be called in other languages. Here is a PHP client program that calls the server-side RPC methods implemented by JsonRPC over a socket connection.

$PHPROOT/jsonrpc.php

<? php



class JsonRPC {



private $conn;



function __construct($host, $port) {

$this->conn = fsockopen($host, $port, $errno, $errstr, 3);

if (! $this->conn) {

return false;

}

}



public function Call($method, $params) {

if (! $this->conn) {

return false;

}

$err = fwrite($this->conn, json_encode(array(

'method' => $method,

'params' => array($params),

'id' => 0,

))."\n");

if ($err === false) {

return false;

}

stream_set_timeout($this->conn, 0, 3000);

$line = fgets($this->conn);

if ($line === false) {

return NULL;

}

return json_decode($line,true);

}

}



$client = new JsonRPC("127.0.0.1", 8096);

$args = array('A'=>9, 'B'=>2);

$r = $client->Call("Arith.Multiply", $args);

printf("%d * %d = %d\n", $args['A'], $args['B'], $r['result']['Pro']);

$r = $client->Call("Arith.Divide", array('A'=>9, 'B'=>2));

printf("%d / %d, Quo is %d, Rem is %d\n", $args['A'], $args['B'], $r['result']['Quo'], $r['result']['Rem']);

Other RPC library

In addition to the above mentioned three ways to implement RPC in Golang, there are a number of other RPC libraries that provide similar functionality, the most famous is Google’s open source GRPC, but the initial installation of GRPC is a bit of trouble, so I won’t introduce it further here, if you are interested, you can learn about it yourself.

The resources

  • Go official library RPC development guide
  • Go Web Development Tutorial -RPC
  • Go RPC Development Guide