Server client programs using Go and gRPC.html

31 May 2021


Recently I have been working on a new project and there was a need to simulate certain test scenarios to validate my design ideas. To achieve that, I felt it would be easier to prototype in Golang and use gRPC for communication between the services. I haven't used gRPC in quite sometime and I thought it would be a good exercise to brush up my memory. So today we will learn to write a simple client and server using gRPC and Golang.

For those who haven't heard of gRPC, gRPC is an RPC(Remote procedure call) mechanism developed by Google and it simplifies the communication between different services across networks. As like any other RPC framework, you can communicate with a service just by calling a method in your program without worrying about the underlying network communication. And also along with the usage of Google Protobuf, the serialization/deserialization of data is also very easy and efficient.

To work with gRPC/Protobuf, you need to define an IDL file(Interface Definition Language) which when compiled using proto compiler(protoc), it will generate the source files for facilitating the RPC communication. The extension of the file should be of ".proto".

Now lets quickly create a folder structure for our project as specified below,

client/
|______ client.go
server/
|______ server.go
proto/
|______ hello.proto
|______ gen/

gen will be the folder where the generated source files from protoc will be kept. We'll now define the proto file as given below,

proto/hello.proto
syntax = "proto3";
option go_package = "github.com/sanojsubran/pipe";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

There are multiple versions of protoc, and today we'll be using version 3 for compiling. The generated Golang files should belong to a package and we specify the package with go_package.

Next we will define the RPC service named Greeter. This will generate SayHello, an rpc call with messages HelloRequest and HelloReply. They are actually two methods with arguments name and message repectively. I wouldn't go into the details of gRPC since it is well documented in gRPC website.

For the installation of protoc and go plugin for protoc, kindly follow the instructions given in the gRPC site.

Now open a terminal and go to the source directory of hello.proto and run the following command,

protoc --go_out=gen --go_opt=paths=source_relative --go-grpc_out=gen --go-grpc_opt=paths=source_relative hello.proto
--go_out : defines the output location of the generated source files for the Protobuf messages
--go_opt : defines the option source_relative which would maitain the path if the proto file is having a complex path. (eg: rpcfile/hello.proto will generate the files with a path rpcfile/ as the parent folder.
--go-grpc : defines the output location of the generated source files for the Service functions.
--go-grpc_opt : defines the option source_relative which would maitain the path if the proto file is having a complex path.
Now you will be able to see the generated Golang source files in the "inline_code">gen/ folder. To resolve the dependency of packages in the source files, go to the gen/ folder and run the following commands in the terminal.

go mod init github.com/sanojsubran/pipe
go mod tidy

Now we will look at the implementation of server.
Open the terminal and go to the location of server/ and run the following the command,

go mod init github.com/sanojsubran/server
The source code for the server will be like this,

server/server.go
package main

import (
"context"
"fmt"
"log"
"net"

"github.com/sanojsubran/pipe"
"google.golang.org/grpc"
)

type server struct {
pipe.UnimplementedGreeterServer
}

func (*server) SayHello(ctx context.Context, request *pipe.HelloRequest) (*pipe.HelloReply, error) {
name := request.Name
response := &pipe.HelloReply{
Message: "Hello " + name,
}
fmt.Printf("\nReceived request with name: %v", name)
return response, nil
}

func main() {
lis, err := net.Listen("tcp", ":8793")
if nil != err {
log.Fatalf("\nError: %v", err)
}
fmt.Printf("\nServer is listening on port 127.0.0.1:8793")
s := grpc.NewServer()
pipe.RegisterGreeterServer(s, &server{})
s.Serve(lis)

}

Now you might have noticed that the package location for the generated package pipe is github.com/sanojsubran/pipe where as the package is available locally inside proto/gen/ location. So inorder to redirect the package location to the local folder, so that we can use in our server, client packages, we need to run the following command from the server/ location,

go mod edit -replace=github.com/sanojsubran/pipe=../proto/gen
After running the command, you could see the relevant changes in the server/go.mod file. Now to resolve the dependency issues, run the following command from server/

go mod tidy
To understand the implementation of gRPC interfaces, you need to look at the source files generated by protoc and implement the relevant interfaces in the server code. To build the server code, run the following command which will the generate the executable,

go build
Now you need to repeat the steps for the client code. So you need to copy the following code to client/client.go and run the following commands,

client/client.go
package main

import (
"context"
"fmt"
"log"
"os"

"github.com/sanojsubran/pipe"
"google.golang.org/grpc"
)

func main() {
opts := grpc.WithInsecure()
cc, err := grpc.Dial("localhost:8793", opts)
if err != nil {
log.Fatalf("Error: %v", err)
}
defer cc.Close()

client := pipe.NewGreeterClient(cc)

request := &pipe.HelloRequest{
Name: "John Lark",
}

resp, err := client.SayHello(context.Background(), request)
if nil != err {
log.Fatal("No response from server")
os.Exit(1)
}
fmt.Printf("Received response: %v", resp.Message)

}

go mod init github.com/sanojsubran/client
go mod edit -replace=github.com/sanojsubran/pipe=../proto/gen
go mod tidy
go build

Now you have the server and client executables generated in their repective folders and upon executing those, you will be able to see the request and response in the console.

Please note that it is important to redirect the location of the pipe package in the go.mod file for server and client by using the go mod edit command.

Code repo:
https://github.com/sanojsubran/go_grpc_tutorial

References:
https://golang.org/doc/tutorial/create-module
https://jbrandhorst.com/post/go-protobuf-tips/
https://grpc.io/docs/languages/go/quickstart/
https://grpc.io/docs/languages/go/basics/