Several ways golang implements RPC

Keywords: encoding PHP JSON Programming

Original Link: https://studygolang.com/articles/14336

Several ways golang implements RPC

https://studygolang.com/articles/14336

What is RPC

Remote Procedure Call (RPC) is a computer communication protocol.This protocol allows programs running on one computer to call subprograms from another without additional programming for this interaction by the programmer.If the software involved is object-oriented, remote procedure calls can also be referred to as remote calls or remote method calls. Wikipedia: Remote Procedure Calls

An easy-to-understand language description is that RPC allows computer program methods to be invoked across machines and languages.For example, I wrote a method getUserInfo to get user information in go language and deployed the Go program on the Ali cloud server. Now I have a php project deployed on Tencent cloud. I need to call golang's getUserInfo method to get user information. The process of php calling go method across machines is RPC call.

How to implement RPC in golang

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

The golang official also provides the net/rpc/jsonrpc library to implement the RPC method, which uses JSON for data encoding and decoding, thus supporting cross-language calls.However, the current jsonrpc library is based on the tcp protocol and does not support data transmission using http at this time.

In addition to the official RPC library provided by golang, there are many third-party libraries to support the implementation of RPC in golang. Most third-party RPC libraries are implemented by using protobuf for data encoding and decoding, automatically generating RPC method definitions and service registration codes based on the protobuf declaration file, which makes it very convenient to perform RPC services in golangCalled.

net/rpc Library

The following example demonstrates how to implement RPC using golang's official net/rpc library, using HTTP as the carrier of RPC, 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"
)

// 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
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)
}

When the above server-side program runs, it will listen on the local port 8095. We can implement a client program, connect the server-side and implement RPC method calls.

$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

The above example demonstrates the process of implementing RPC using net/rpc, but it is not possible to call the RPC method implemented in the above example in another language.So the next example demonstrates using the net/rpc/jsonrpc library to implement the RPC method, which 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
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 requests
        if err != nil {
            continue
        }

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

When the above server-side program starts, it will listen to the local port 8096 and process the client's tcp connection requests.We can use golang to implement a client program that connects the above servers 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 // 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 achieve 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 it also has some drawbacks, such as not supporting http transmission and low data encoding and decoding performance.Thus, some third-party RPC libraries choose to use protobuf for data encoding and decoding, and provide some automatic generation of service registration codes.The following example uses protobuf to define RPC methods and request response parameters, and third-party protorpc libraries to generate RPC service registration codes.

First, you need to install protobuf and the protoc executable commands, which you can refer to in this article: protobuf Quick Upper Finger Guide

Then, we write a proto file that defines the RPC method to be implemented and its associated 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
    rpc divide (ArithRequest) returns (ArithResponse);      // Division operation method
}

Next we need to generate the RPC service code from the arith.proto file defined above.
To install the protorpc library first: go get github.com/chai2010/protorpc
Then use the protoc tool to generate the code: protoc --go_out=plugin=protorpc=. arith.proto
After executing the protoc command, an arith.pb.go file containing the code for the RPC method definition and service registration was generated in a directory equivalent to the arith.proto file.

We implement an rpc server based on the generated arith.pb.go code

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

package main

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

// Arithmetic operation structure
type Arith struct {
}

// Multiplication
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 port 8097 and receive tcp connections from clients.

Implement another client based on ariti.pb.go.

$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

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

In addition to the three ways mentioned above to implement RPC in golang, there are other RPC libraries that provide similar functionality. The well-known open source grpc is google, but the initial installation of grpc is more cumbersome and will not be further described here. Those who are interested can learn about it themselves.

Reference material

This article is from: Short Book

Thank you for the author: Douban milk tea

View the original text: Several ways golang implements RPC

Posted by ncracraf on Sun, 18 Aug 2019 19:01:03 -0700