Several ways of realizing RPC by golang

What is RPC

Remote Procedure Call (RPC) is a computer communication protocol. The protocol allows a program running on one computer to call a subroutine of another computer without extra programming for this interaction. If the software involved adopts object-oriented programming, Remote Procedure Call can also be called remote call or remote method call.

Described in easy to understand language: RPC allows cross machine and cross language calls to computer program methods. For example, I wrote a method getUserInfo to obtain user information in go language and deployed the Go program on Alibaba cloud server. Now I have a php project deployed on Tencent cloud. I need to call getUserInfo method of golang to obtain user information. The process of php inter machine calling go method is RPC call.

How to implement RPC in golang

It is very simple to implement RPC in golang, supported by encapsulated official libraries and some third-party libraries. Go RPC can use tcp or http to transmit data, and can use various types of encoding and decoding methods for the data to be transmitted. The official net/rpc Library of golang uses encoding/gob for encoding and decoding and supports tcp or http data transmission mode. Because other languages do not support gob encoding and decoding mode, the RPC method implemented by net/rpc library cannot be called across languages.

golang also officially provides the net/rpc/jsonrpc library to implement RPC methods. JSON RPC uses JSON for data encoding and decoding, so it supports cross language calls. However, the current JSON RPC library is implemented based on tcp protocol and does not support data transmission using http for the time being.

In addition to the RPC libraries officially provided by golang, there are many third-party libraries that support the implementation of RPC in golang. Most of the third-party RPC libraries use protobuf for data encoding and decoding, and the RPC method definition and service registration code are automatically generated according to the protobuf declaration file. It is very convenient to call RPC services in golang.

net/rpc Library

The following example demonstrates how to use the official net/rpc Library of golang to implement the RPC method, use http as the carrier of RPC, and listen 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"
)

// 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 // merchant
    Rem int // remainder
}

// Multiplication method
func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {
    res.Pro = req.A * req.B
    return nil
}

// Division operation method
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 rpc service
    rpc.HandleHTTP()         // Using http protocol as 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 to the local port 8095. We can implement a client program to connect to the server and call RPC methods.

$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 // merchant
    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) // Multiplication
    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

In the above example, we demonstrate the process of using net/rpc to implement RPC, but we can not invoke the RPC method implemented in the above examples in other languages. So in the following example, we will demonstrate how to use the net/rpc/jsonrpc library to implement the RPC method. The RPC method implemented in this way supports cross language calls.

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

package main

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

// 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 // merchant
    Rem int // remainder
}

// Multiplication method
func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {
    res.Pro = req.A * req.B
    return nil
}

// Division operation method
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 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() // Receive client connection request
        if err != nil {
            continue
        }

        go func(conn net.Conn) { // Client request concurrent processing
            fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")
            jsonrpc.ServeConn(conn)
        }(conn)
    }
}

After the above server program is started, it will listen to the local port 8096 and process the tcp connection request of the client. We can use golang to implement a client program to connect the above server and make 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 // merchant
    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) // Multiplication
    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 realize cross language call, when implementing RPC method in golang, we should choose a cross language data encoding and decoding method, such as JSON. The above jsonrpc can meet this requirement, but there are also some disadvantages, such as not supporting http transmission and low data encoding and decoding performance. Therefore, some third-party RPC libraries choose to use protobuf for data encoding and decoding, and provide some functions of automatic generation of service registration code. In the following example, we use protobuf to define the RPC method and its request response parameters, and use the third-party protorpc library to generate the RPC service registration code.

First, you need to install protobuf and protoc executable commands. You can refer to this article: protobuf quick start guide

Then, we write a proto file to define the RPC method to be implemented and its related parameters.

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

syntax = "proto3";package pb;  // Arithmetic operation request structure
message ArithRequest {    int32 a = 1;    int32 b = 2;}// Arithmetic operation response structure

message ArithResponse {    
    int32 pro = 1;  // product
    int32 quo = 2;  // merchant
    int32 rem = 3;  // remainder
}// rpc method

service ArithService {    
rpc multiply (ArithRequest) returns (ArithResponse);  // Multiplication method
rpc divide (ArithRequest) returns (ArithResponse);   // Division operation method
}

Next, we need arith The proto file generates RPC service code. First install the protorpc Library: go get GitHub COM / chai2010 / protorpc, and then use the protocol tool to generate the code: protocol -- go_ out=plugin=protorpc=. arith. After proto executes the protoc command, it is connected with arith The proto file generates an arith pb. Go file, which contains the code of RPC method definition and service registration.

Based on the generated arith pb. Go code, let's implement an rpc server

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

package main

import (
    "errors"
    "test/rpc/pb"
)

// Arithmetic operation structure
type Arith struct {
}

// Multiplication method
func (this *Arith) Multiply(req *pb.ArithRequest, res *pb.ArithResponse) error {
    res.Pro = req.GetA() * req.GetB()
    return nil
}

// Division operation method
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", new(Arith))
}

Running the above program will listen to the local 8097 port and receive the tcp connection of the client.

Based on ariti pb. Go and implement 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 to call golang's RPC method across languages

In the above three examples, we use net/rpc, net/rpc/jsonrpc and protorpc to implement the RPC server in golang respectively, and give the corresponding examples of the RPC call of golang client. Because JSON and protobuf support multiple languages, we can call the RPC methods implemented by jsonrpc and protorpc in other languages. The following is a php client program that calls the server RPC method implemented by jsonrpc through 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 Libraries

In addition to the three ways of implementing RPC in golang mentioned above, there are other RPC libraries that provide similar functions. The famous one is google's open source grpc, but the initial installation of grpc is troublesome. I won't introduce it further here. Those who are interested can understand it for themselves.

Added by bloodl on Thu, 17 Feb 2022 06:02:25 +0200