DEV Community

elesq
elesq

Posted on

RPC with Go, what is it?

OK, so we're going to have a look at RPC with Go. So what is RPC? RPC (remote procedure call) is a distributed computing concept where a call and response mechanism applies to the execution of a function to achieve an outcome and receive a result.

Say what?

But what does that mean? Well, we could look at it as follows:

  • We have a client who preps a function name and the arguments required to be sent with it.
  • An RPC server hosting this function receives this.
  • The server executes the remote process request.
  • The server responds to the client with the outcome and result.
  • the client receives the response (outcome) and data (result) and goes about its business merrily on to the next task.

OK. Show me in Go

morpheus show me

Prerequisites

  • The server needs to expose the service. This is needed for the client to call and connect to the service.
  • go provides two libraries of interest net/rpc and net/jsonrpc.

We're going to have a project that contains two sub-projects, so let's create a folder for the project and within it create two subfolders, client and server.

Ideally we now have a structure that looks like the following:

rpc-banner-demo
├── client
│   └── main.go
└── server
    └── main.go
Enter fullscreen mode Exit fullscreen mode

The RPC server

We're going to create an RPC server with a function that returns a message that could be used as a banner message or some other purpose for which you need to return a string.

package main

import (
    "log"
    "net"
    "net/http"
    "net/rpc"
)

// an RPC server in Go

type Args struct{}

type BannerMessageServer string

func (t *BannerMessageServer) GetBannerMessage(args *Args, reply *string) error {
    *reply = "This is a message from the RPC server"
    return nil
}

func main() {

    // create and register the rpc
    banner := new(BannerMessageServer)
    rpc.Register(banner)
    rpc.HandleHTTP()

    // set a port for the server
    port := ":1122"

    // listen for requests on 1122
    listener, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatal("listen error: ", err)
    }

    http.Serve(listener, nil)
}
Enter fullscreen mode Exit fullscreen mode

Things to note:

  • we're creating a function called GetBannerMessage of the BannerMessageServer type.
  • we need to register it.
  • Our server requires a port, 1122 in our case.

The RPC client

Now we create the client that will call our server and handle/use the response and result.

package main

import (
    "log"
    "net/rpc"
)

// rpc client

type Args struct{}

func main() {

    hostname := "localhost"
    port := ":1122"

    var reply string

    args := Args{}

    client, err := rpc.DialHTTP("tcp", hostname+port)
    if err != nil {
        log.Fatal("dialing: ", err)
    }

    // Call normally takes service name.function name, args and
    // the address of the variable that hold the reply. Here we
    // have no args in the demo therefore we can pass the empty
    // args struct.
    err = client.Call("BannerMessageServer.GetBannerMessage", args, &reply)
    if err != nil {
        log.Fatal("error", err)
    }

    // log the result
    log.Printf("%s\n", reply)
}
Enter fullscreen mode Exit fullscreen mode

Things to note:

  • note that we have to specify the hostname and port in our client. localhost and :1122 in our demo case.
  • note that we have an empty args struct because we don't need it for our demo. If we needed it we can define the argument struct at the top of the file and make more complex use of our setup.
  • we call the service.function and the reply will be assigned to the address of of our local variable reply.
  • we simply log the outcome and end. In a more complex example we might act on this data, it may even feed into another RPC function call.

Run, Forest, run...

OK so now we're in a position to run our code and see this magic unfold before our eyes. There is a couple of options here, I'm opting to run both programs from the root folder of our project. We'll need two terminals, so here you can split your terminal in VSCode.

in one run go run server/main.go, in the other run go run client/main.go and marvel at your results

I hope you found this fairly trivial RPC example useful as a RPC client/server setup overview and as a potential springboard to your own functions and setups that actually have a real purpose.

Best wishes.

Discussion (2)

Collapse
arroyoruy profile image
Dan Arroyo

Thank you so much! it is the simple examples that explain so much. I am falling in love so hard with Go and this is one of the reasons why.

Collapse
iamelesq profile image
elesq Author

Thank you Dan.