what is gRPC?
To understand gRPC first we need to know what RPC is.RPC
(Remote Procedure Call) is used to create Inter-Process-Communication between services where services can communicate with each other remotely. And gRPC is an open-source high-performance RPC framework that was implemented by Google. It is widely used in a microservices architecture.
Why gRPC?
gRPC is way faster than any RPC or REST API. It uses HTTP/2 for high-scale performance and by default, gRPC uses a protocol buffer for data serialization which makes the payload faster than using any other JSON or XML to format the data cause the protocol buffer uses binary as payload (smaller and faster). gRPC also provides bidirectional streaming.
Types of gRPC Communication
there are four types of gRPC communication
Unary
where the client sends a request to the server and gets a single response back.
Server Streaming
where the client sends a request to the server and gets a stream of responses from the server. It's similar when we request to watch a video on youtube and then youtube sends chunks of video as a response.
Client Streaming
where the client writes a stream of messages and sends them to the server and the server returns the response. when we upload data to a server.
Bidirectional Streaming
where the client and server both send messages to each other. when a server gets a request from the client and sends back a response then the client sends another request according to the response.
Getting started with GRPC in Golang
The first step is to install the Go plugins for the protocol compiler:
Install the protocol compiler plugins for Go using the following commands:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.22
Install the protocol compiler plugins for Go using the following commands:
export PATH="$PATH:$(go env GOPATH)/bin"
Now create a folder named "proto" and then create a file named "greet.proto". Next, we need to configure our ".proto" file then protoc command will generate the code based on the desired language. A schema for a particular use of protocol buffers associates data types with field names, using integers to identify each field. here's the code for greet.proto
// version of protocol buffer
syntax="proto3";
// declaring go import path
option go_package = "./proto";
// importing this package for empty type declaration
import "google/protobuf/empty.proto";
package greet_service;
service GreetService{
// unary communication
rpc SayHello(google.protobuf.Empty) returns (HelloResponse);
// server streaming communication
rpc SayHelloServerStreaming(NameList) returns (stream HelloResponse);
// client streaming communication
rpc SayHelloClientStreaming(stream HelloRequest) returns (MessageList);
// bidirectional streaming communication
rpc SayHelloBidirectionalStreaming(stream HelloRequest) returns (stream HelloResponse);
}
message HelloRequest{
string name = 1;
}
message NameList{
// "repeated" represents array here
repeated string message = 1;
}
message HelloResponse{
string message = 1;
}
message MessageList{
repeated string messages = 1;
}
run protoc --go_out=. --go-grpc_out=. proto/greet.proto at the root of the directory this command will generate client and server stubs in the proto folder.
Server-side
create a server folder and main.go file
// server/main.go
package main
import (
"log"
"net"
pb "github.com/IRSHIT033/go-grpc/proto"
"google.golang.org/grpc"
)
// define the port
const (
port = ":8080"
)
// struct that represents the rpc server
type helloServer struct {
// protocol buffer generated server struct
pb.GreetServiceServer
}
func main() {
// listen on the port
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("Failed to start the server %v", err)
}
// creating new gRPC server
grpcServer := grpc.NewServer()
// register the server
pb.RegisterGreetServiceServer(grpcServer, &helloServer{})
log.Printf("server started at %v", lis.Addr())
// now gRPC server accepts the request from the client
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to start: %v", err)
}
}
Client-side
create a client folder and main.go file
// client/main.go
package main
import (
"log"
pb "github.com/IRSHIT033/go-grpc/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
port = ":8080"
)
func main() {
// creates a client connection to the server
conn, err := grpc.Dial("localhost"+port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect : %v", err)
}
defer conn.Close()
// created gRPC client
client := pb.NewGreetServiceClient(conn)
}
both server and client setup is done
Unary Communication
this code will create a unary communication in grpc between client and server.
// server/unary.go
// emptypb.Empty for empty type struct
// same as the rpc function in the proto
func (s *helloServer) SayHello(ctx context.Context, in *emptypb.Empty) (*pb.HelloResponse, error) {
return &pb.HelloResponse{
Message: "hello",
}, nil
}
// client/unary.go
func callSayHello(client pb.GreetServiceClient) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// emptypb.Empty for empty type struct
resp, err := client.SayHello(ctx, &emptypb.Empty{})
if err != nil {
log.Fatalf("could not greet : %v", err)
}
log.Printf("%s", resp.Message)
}
and then call the callSayHello(client pb.GreetServiceClient) in the client/main.go
Client-Side-Streaming
// server/client_stream.go
func (s *helloServer) SayHelloClientStreaming(stream pb.GreetService_SayHelloClientStreamingServer) error {
var messages []string
for {
req, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.MessageList{Messages: messages})
}
if err != nil {
return nil
}
log.Printf("Got request with names: %v", req.Name)
messages = append(messages, "Hello ", req.Name)
}
}
// client/client_stream.go
func callSayHelloClientStream(client pb.GreetServiceClient, names *pb.NameList) {
log.Printf("client streaming started ")
stream, err := client.SayHelloClientStreaming(context.Background())
if err != nil {
log.Fatalf("could not send names: %v", err)
}
for _, name := range names.Message {
req := &pb.HelloRequest{
Name: name,
}
if err := stream.Send(req); err != nil {
log.Fatalf("error while sending %v", err)
}
log.Printf("send the request with name: %s", name)
time.Sleep(2 * time.Second)
}
res, err := stream.CloseAndRecv()
log.Printf("client streaing finished")
if err != nil {
log.Fatalf("error while receiving %v", err)
}
log.Printf("%v", res.Messages)
}
and then call the callSayHelloClientStream(client pb.GreetServiceClient, names *pb.NameList) in the client/main.go
Server-Side-Streaming
// server/server_stream.go
func (s *helloServer) SayHelloServerStreaming(req *pb.NameList, stream pb.GreetService_SayHelloServerStreamingServer) error {
log.Printf("got request with names : %v", req.Message)
for _, name := range req.Message {
res := &pb.HelloResponse{
Message: "Hello " + name,
}
if err := stream.Send(res); err != nil {
return err
}
time.Sleep(2 * time.Second)
}
return nil
}
// client/server_stream.go
func callSayHelloServerStream(client pb.GreetServiceClient, names *pb.NameList) {
log.Printf("Streaming started")
stream, err := client.SayHelloServerStreaming(context.Background(), names)
if err != nil {
log.Fatalf("could not send names: %v", err)
}
for {
message, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("error while running %v", err)
}
log.Println(message)
}
log.Println("streaming finished")
}
and then call the callSayHelloServerStream(client pb.GreetServiceClient, names *pb.NameList) in the client/main.go
Bidirectional-Streaming
// server/bidirectional_stream.go
func (s *helloServer) SayhelloBidirectionalStreaming(stream pb.GreetService_SayHelloBidirectionalStreamingServer) error {
for {
req, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
log.Printf("Got the request with name:%v", req.Name)
res := &pb.HelloResponse{
Message: "Hello" + req.Name,
}
if err := stream.Send(res); err != nil {
return err
}
}
}
// client/bidirectional_stream.go
func callSayHelloBidirectionalStream(client pb.GreetServiceClient, names *pb.NameList) {
log.Printf("Bidirectional streaming started")
stream, err := client.SayHelloBidirectionalStreaming(context.Background())
if err != nil {
log.Fatalf("could not send names : %v", err)
}
// creating a channel
waitc := make(chan struct{})
// creating a go routine function receives data stream from server
go func() {
for {
message, err := stream.Recv()
if err == io.EOF {
// get out infinite loop when there's not any response from the server
break
}
if err != nil {
log.Fatalf("error while running %v", err)
}
log.Println(message)
}
// closing the channel
close(waitc)
}()
for _, name := range names.Message {
req := &pb.HelloRequest{
Name: name,
}
// sends requests to the server
if err := stream.Send(req); err != nil {
log.Fatalf("Error while sending %v", err)
}
// using sleep for for the delay to
time.Sleep(2 * time.Second)
}
stream.CloseSend()
// block until the channel is closed
<-waitc
log.Printf("Bidirectional streaming finished")
}
and then call the callSayHelloBidirectionalStream(client pb.GreetServiceClient, names *pb.NameList) in the client/main.go
here's the GitHub code:- GRPC_GO_project