Implement reverse RPC calls in Go

By | October 22, 2019

Generally, RPC is based on the C/S structure. The RPC server corresponds to the network server, and the RPC client corresponds to the network client. However, for some unique scenarios, such as providing an RPC service on the company’s intranet, but unable to connect to the intranet’s server on the external network. At this time, we can refer to the technology similar to reverse proxy. First, we actively link from the internal network to the TCP server of the external network and then provide RPC services to the external network based on the TCP link.

Declare interface

type HelloService struct {}
func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello:" + request
    return nil
}

Reverse RPC calls implementation

func main() {
    rpc.Register(new(HelloService))
    for {
        conn, _ := net.Dial("tcp", "localhost:1234")
        if conn == nil {
            time.Sleep(time.Second)
            continue
        }
        rpc.ServeConn(conn)
        conn.Close()
    }
}

Reverse RPC’s intranet service will no longer actively provide TCP listening services, but will first actively link to the other party’s TCP server. Then provide RPC services to each other based on each established TCP link.

RPC client

The RPC client needs to provide a TCP service at a public address to accept the link request of the RPC server:

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

    clientChan := make(chan *rpc.Client)

    go func() {
        for {
            conn, err := listener.Accept()
            if err != nil {
                log.Fatal("Accept error:", err)
            }

            clientChan <- rpc.NewClient(conn)
        }
    }()

    doClientWork(clientChan)
}

An RPC client object is constructed after each link is established based on the network link and sent to the clientChan pipeline. The client performs the RPC call operation in the doClientWork function:

func doClientWork(clientChan <-chan *rpc.Client) {
    client := <-clientChan
    defer client.Close()

    var reply string
    err = client.Call("HelloService.Hello", "hello", &reply)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(reply)
}

First, fetch an RPC client object from the pipeline, and specify to close the client before the function exits through the defer statement. Then the regular RPC call is executed.