DEV Community

Cover image for Beginner's Guide to RPC in Golang: Understanding the Basics
atanda nafiu
atanda nafiu

Posted on • Edited on

Beginner's Guide to RPC in Golang: Understanding the Basics

In the Information Exchange Model, the client sends a request, and the server responds to the client. The type of data/information exchange is based on a specific format that lets both sides communicate with each other. Presently most applications do not use this model to share but instead call services, as they would call any function.

Remote Procedure Call(RPC) was intended to be the function model for networked systems. Client execute RPC like they call a native function, except client package the function parameter and send them through the network to the server. The server can then unpack this parameter and process the request, executing results to the client.

What is RPC

A Remote Procedure Call is an interprocess communication that exchanges data/information between the sender and receiver in a distributed system. RPC is used in client/server architecture for accessible communication. The primary objective of RPC is to develop the physical and logical layers.

The following list is a basic overview of how the RPC process works:

  • The RPC client puts together the function and argument required to be sent to the server
  • The RPC server receives them by dialling the connection
  • The RPC server executes the remote process
  • The RPC server responded to the client with the result
  • The RPC client gets the response from the request duly and uses it then, it moves to the next task.

Prerequisites

Before getting started, ensure you have the following prerequisites:

  • Basic knowledge of Go
  • Go installed on your machine

Step 1: Setting Up Your Project

To begin building your project, follow these steps to set up your project:

Create a new directory for your project, open your terminal, and run the following command:

mkdir -p go-RPC
Enter fullscreen mode Exit fullscreen mode

This will create a new folder name go-RPC in your current directory.

Navigate to the project director by running the following command:

cd go-RPC
Enter fullscreen mode Exit fullscreen mode

Now you are inside the go-RPC directory.

Initialize your new project by running the following command:

go mod init go-RPC
Enter fullscreen mode Exit fullscreen mode

This command creates a new go.mod file, your project's configuration, and the dependency management file.

Following these steps, you will have a new project directory with a go.mod file ready.

This project will have two sub-directory; follow the steps in the preceding to create the sub-directory. You will have an RPC server and RPC client folder that looks like this.

client-server-RPC
├── client
│    └── main.go
└── server
     └── main.go
Enter fullscreen mode Exit fullscreen mode

Step 2: Create The RPC Server

You will create an RPC server to send UTC server time to the RPC client. To build the RPC server, go has two libraries, net/rpc and net/http.

package main
import (
    "log"
    "net"
    "net/http"
    "net/rpc"
    "time"
)
type Args struct{}
type TimeServer int64
func (t *TimeServer) GiveServerTime(args *Args, reply *int64) error {
    // Fill reply pointer to send the data back
    *reply = time.Now().Unix()
    return nil
}
func main() {
    // Create a new RPC server
    timeserver := new(TimeServer)
    // Register RPC server
    rpc.Register(timeserver)
    rpc.HandleHTTP()
    // Listen for requests on port 1234
    l, e := net.Listen("tcp", ":2233")
    if e != nil {
        log.Fatal("listen error:", e)
    }
    http.Serve(l, nil)
}
Enter fullscreen mode Exit fullscreen mode

Explanation:
The Args struct holds information about arguments passed from the client (RPC) to the server. TimeServer number to register with the rpc.Register. Here, the server wishes to export an object of type TimeServer(int64). HandleHTTP registers an HTTP handler for RPC messages to DefaultServer. Then you started a TCP server that listens on port 2233—the http.Serve function is used to serve it as a running program. GiveServerTime is the client's function, and the current server time is returned.

Here are a few points to note:

  • Args struct has no field because the server code is not expecting any argument from the client
  • GiveServerTime takes the Args objects and replies pointer object
  • it sets the reply pointer object
  • It serves on port 2233

Step 3: Create The RPC Client

You'll create the client to call your server and handle the response here. It also uses the same net/rpc library but with a different method to dial the server.

package main
import (
    "log"
    "net/rpc"
)
type Args struct {
}
func main() {
    var reply int64
    args := Args{}
    client, err := rpc.DialHTTP("tcp", "localhost"+":2233")
    if err != nil {
        log.Fatal("dialing:", err)
    }
    err = client.Call("TimeServer.GiveServerTime", args, &reply)
    if err != nil {
        log.Fatal("arith error:", err)
    }
    log.Printf("%d", reply)
}
Enter fullscreen mode Exit fullscreen mode

Explanation:
The client DialHTTP to connect to the RPC server, which is running on port 2233. Call the Remote function with the Name: function format with args and reply with the pointer object, the client get the data collected into the reply object and call function is sequential.

Note: The client is running as an independent program. Both programs can be on different machines, and the computing can still be shared. This is the core concept of distributed systems. The tasks are divided and given to various RPC servers. Finally, the client collects the results and uses them for further actions. Custom RPC code is only valid when the client and server are written in Go. So to have the RPC server consumed by multiple services, we need to define the JSON RPC over HTTP. Then, any other programming language can send a JSON string and get JSON.

Now you will start the server.go to the program:

go run server.go
Enter fullscreen mode Exit fullscreen mode

Open another terminal tab to start the client.go program:

go run client.go
Enter fullscreen mode Exit fullscreen mode

The client console will give an output of the current UTC.

The output of the terminal

JSON RPC Using Gorilla RPC

Gorilla also provides an RPC library. It makes it easier for the client and server to be in the same file. You can send a request as JSON and receive a response as JSON.

This section will be a real-world use case. You have a JSON file on the server with Twitter profile details (Name, username, followers, and following). The client requests Twitter information by making an HTTP request. When the RPC server receives the request, it reads the file from the filesystem and parses it. If the Name matches any profile, the server returns the information to the client in JSON format.

You will install Gorilla RPC and Mux library with the following command:

go get github.com/gorilla/rpc


go get github.com/gorilla/mux
Enter fullscreen mode Exit fullscreen mode

Your folder structure should look like this.

JSON-RPC
├── go.mod
├── go.sum
├── twitterProfile.json
└── server
     └── main.go
Enter fullscreen mode Exit fullscreen mode

The JSON data file
Here is a sample data file with a twitterprofile details structure in it.

[
    {
        "name": "Name cannot be black",
        "username": "@hackSultan",
        "followers": "187k",
        "following": "974"
    },
    {
        "name": "Scott Hanselman",
        "username": "@shanselman",
        "followers": "317k",
        "following": "10.5k"
    },
    {
        "name": "Odogwu Machalla",
        "username": "@unicodeveloper",
        "followers": "97k",
        "following": "15.5k"
    },
    {
        "name": "Gbedilodo",
        "username": "@olodocoder",
        "followers": "252",
        "following": "143"
    },
    {
        "name": "Angie Jones",
        "username": "@techgirl908",
        "followers": "114k",
        "following": "626"
    }
]
Enter fullscreen mode Exit fullscreen mode

Implementing The RPC JSON Server

In this example, you must make a few changes to the preceding server example. Including Gorilla
libraries in this example.

package main
import (
    jsonparse "encoding/json"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "github.com/gorilla/mux"
    "github.com/gorilla/rpc"
    "github.com/gorilla/rpc/json"
)
// Args holds arguments passed to JSON RPC service
type Args struct {
    Name string
}
// TwitterProfie struct holds TwitterProfile JSON structure
type TwitterProfile struct {
    Name     string `json:"name,omitempty"`
    Username   string `json:"username,omitempty"`
    Followers string `json:"followers,omitempty"`
    Following string `json:"following,omitempty"`
}
type JSONServer struct{}
// TwitterProfileDetail
func (t *JSONServer) TwitterProfileDetail(r *http.Request, args *Args, reply *TwitterProfile) error {
    var twitterprofiles []TwitterProfile
    // Read JSON file and load data
    raw, readerr := ioutil.ReadFile("./twitterprofile.json")
    if readerr != nil {
        log.Println("error:", readerr)
        os.Exit(1)
    }
    // Unmarshal JSON raw data into twitterprofiles array
    marshalerr := jsonparse.Unmarshal(raw, &twitterprofiles)
    if marshalerr != nil {
        log.Println("error:", marshalerr)
        os.Exit(1)
    }
    // Iterate over each twitterprofile to find the given twitterprofile
    for _, twitterprofile := range twitterprofiles {
        if twitterprofile.Id == args.Id {
            // If twitterprofile found, fill reply with it
            *reply = twitterprofile
            break
        }
    }
    return nil
}
func main() {
    // Create a new RPC server
    s := rpc.NewServer() // Register the type of data requested as JSON
    s.RegisterCodec(json.NewCodec(), "application/json")
    // Register the service by creating a new JSON server
    s.RegisterService(new(JSONServer), "")
    r := mux.NewRouter()
    r.Handle("/rpc", s)
    http.ListenAndServe(":9000", r)
}
Enter fullscreen mode Exit fullscreen mode

Open a terminal to start the server program:

go run main.go
Enter fullscreen mode Exit fullscreen mode

Explanation:
The args and TwitterProfile structs contain information about the JSON arguments passed and the profile structure. You define a remote function called TwitterProfileDetail on a resource called JSONServer. This struct is a service created to register with the RegisterService function of the RPC server. If you notice, you also write the codec as JSON. Whenever you get a request from the client, You load the JSON file called twitterprofile.json into memory and then into the TwitterProfile struct using JSON's Unmarshal method. jsonparse is the alias given for the Go package encoding/json because the JSON package from the Gorilla import has the same Name to remove conflict, use an alias instead.

In the remote function, you set the value of the reply with the matched twiterprofile. The data is filled if the client's name matches any of the twitterprofiles in JSON. If there is no match, the RPC server will return empty data. This way, you can create a JSON RPC to make clients universal. Here, no Go client was written. Any client can access data from the service.

Now that the server is running, you must interact with the client. The client can be a CRUL command since the RPC server is serving a request over HTTP. The twitterprofile name is required to get the details.

Open another terminal with the following command to execute the CRUL request:

curl -X POST \
http: //localhost:9000/rpc \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{
"method": "JSONServer.TwitterProfileDetail",
"params": [
{
"name":"Name cannot be blank"
}
],
"id": "1"
}'
Enter fullscreen mode Exit fullscreen mode




Conclusion

You've learned what an RPC is and how an RPC server and client can be built. The JSON RPC is used as a use case, and the use case JSON RPC uses the Gorilla toolkit. Now you can easily create RPC in your Golang project.

For a better understanding, kindly check more articles on RPC. I hope you enjoy this article. Leave a comment on the part you didn’t understand and smash the like button.

Note:
Prefer JSON RPC when multiple client technologies need to connect to your service.
You can view the source code here.

I'd love to connect with you on. Twitter | Linkedin | Github

Top comments (4)

Collapse
 
harmlessprince profile image
Adewuyi Taofeeq Olamilekan

Cool write up

Collapse
 
atanda0x profile image
atanda nafiu

Thank you boss!!!!

Collapse
 
erikkalkoken profile image
Erik Kalkoken

Thank you for the article. I found the examples very useful as starting point for understanding how to build a project with RPC.

To make it even better may I suggest three additions?

  1. Would make it clearer in the text that the 1st part is about implementing RPC with Go's standard library only, while the 2nd part is using a 3rd party library.
  2. Would add a direct link to the the rpc package of the standard library for reference and further reading
  3. Would mention that Go's rpc package is frozen, so people know what they get into. i.e. the package works fine, but current bugs will not be fixed and missing features will not be added
Collapse
 
steveja profile image
Steve Alexander

In the first example, the command to execute would be ...

go run server/main.go &
go run client/main.go