29
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.
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.
- 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
andnet/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
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)
}
Things to note:
- we're creating a function called
GetBannerMessage
of theBannerMessageServer
type. - we need to register it.
- Our server requires a port, 1122 in our case.
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)
}
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 variablereply
. - 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.
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.
29