
Here we will get to the heart of the matter with an example of using gRPC and using first the GO language, so I start the study and that you can find in posts dedicated to the study of this powerful and pleasant Google language.
For my part, I use the following tools most simply in the world:
For compiling proto in GO and gRPC for GO, you will also need to install:
This basic demo will introduce the following different concepts:
The functional purpose of the demo is not really of interest, it is mainly the implementation, the consumption of it. So to put it simply, I left on a service providing a set of basic calculation operations, such as: adding, subtracting and flipping a multiplication table some conch.
Here we will write all the descriptions of the messages and operations of our service using this description language. It will allow us to generate the code serving server side but also the code serving client side that can be called the service contract.
syntax = "proto3"; option go_package = "fredericschmidt.fr/GoDemo01/calculatrice"; package Calculator; service CalculatorService { rpc AddOperation (ValuesCalculatorRequest) returns (ResultCalculatorResponse); rpc SubOperation (ValuesCalculatorRequest) returns (ResultCalculatorResponse); rpc TableOperation (TableCalculatorRequest) returns (TableCalculatorResponse); } message ResultCalculatorResponse { int32 Result = 1; } message ValuesCalculatorRequest { int32 TermX = 1; int32 TermY = 2; } message TableCalculatorRequest { int32 Multiplicand = 1; int32 Multiplier = 2; } message TableCalculatorResponse { repeated OneLineInTableResponse LineOfTable = 1; } message OneLineInTableResponse { int32 Multiplicand = 1; int32 Multiplier = 2; int32 Product = 3; }
Afterwards, we have to generate the code in GO language in our case and to do this we will execute the following command:
protoc --go_out="D:\TempDev\Lab\gRpc\goroot\src"" .\Calculatrice.proto
Then the command protoc must be if the installation was done in the directory of the variable GOBIN.
For the same reason we should also find the protoc-gen-go.exe and protoc-gen-go-grpc.exe commands. When the generation was successfully performed using the protoc command, which takes two parameters that happen to be:
After that, we have to generate from the previous step the file in GO language specific to gRPC and to do this we will use the following command:
protoc --go-grpc_out="D:\TempDev\Lab\gRpc\goroot\src" .\Calculatrice.proto
with the following parameters:
At the end of these two steps, we obtain two GO files:
calculatrice_grpc_pb.go ;
calculatrice.pb.go.
We will come back to the content of these two files in more detail later.
To be able to use the previously generated code we need to develop two distinct parts:
For the implementation code, I leave you to look directly into the sources that will be available on my GitHub.
In our demonstration, the server part will implement the following methods:
type CalculatorServiceServer interface { AddOperation(context.Context, \*ValuesCalculatorRequest) (\*ResultCalculatorResponse, error) SubOperation(context.Context, \*ValuesCalculatorRequest) (\*ResultCalculatorResponse, error) TableOperation(context.Context, \*TableCalculatorRequest) (\*TableCalculatorResponse, error) mustEmbedUnimplementedCalculatorServiceServer() }
The next step to start and listen to the gRPC server and for this we will use the GO package about the gRPC.
We will install it using the following GO command:
go get google.golang.org/grpc
Here is the code of the server part:
fmt.Println("Starting Calculator Server gRPC") lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("ERROR : failed to listen on port %d : %v", port, err) } srv := grpc.NewServer() myPb.RegisterCalculatorServiceServer(srv, &CalculatorServer{}) if err := srv.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) }
Some explanations on some lines of the code.
lis, err := net.Listen("tcp", port)
Lets you tap all the addresses available on the machine (because we didn’t specify a local address) on a specific port in a defined protocol and wait for requests.
srv := grpc.NewServer()
Asks gRPC to create a server or rather an empty shell, as there will not yet be a defined service and it will not start. And so will not yet be able to accept and process applications.
myPb.RegisterCalculatorServiceServer(srv, &CalculatorServer{})
If we look at the code generated from the IDL, we see what is called on the following line:
s.RegisterService(&_CalculatorService_serviceDesc, srv)
It allows to declare and record for our empty server shell the service contract and its implementation all this being contained in the code generated from the IDL. It is essential to do this before anything else.
The last step is to start the server so that it can process requests. Here is the part of code with a write specific to GO in the condition:
if err := srv.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) }
The instruction we are interested in is as follows: :
err := srv.Serve(lis);
She will simply tune in to our listener and process incoming requests. Each request (Transportation and Service) will be processed in a GB Routine (see GO language ticket). If a fatal error occurs during a request, the server will be closed and a fatal error will be reported.
For our demonstration, to stop the server you simply have to perform a [CTRL]+[C].
For the implementation code, I leave you to look directly into the sources that will be available on my GitHub.
I will only dwell on the parts of code specific to gRPC, the rest being more to read in the posts that are about the GO language and its specificities. In particular, on the use of the package flag for processing command line arguments.
cnx, err := grpc.Dial(uri, grpc.WithInsecure(), grpc.WithBlock())
Establish a connection to a server located at uri, in our case it will be localhost:666 and with the following options:
WithInsecure(): to disable transport security;
WithBlock(): to request that the command be blocking, because if this is not specified. The instruction returns the hand immediately and the connection is made in the background. Here we want to skip to the sequel only if the connection was made or an error.
c:= pb.NewCalculatorServiceClient(cnx)
We instantiate a client we could also talk about proxy in other languages, to our server since we have established a connection cnx.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
Allows to set up a Timeout if our processing in our main context, in our case, our func main passes the second. If this exceeds a DeadlineExceed error, the current call will stop. You can simply check this by setting time.Microsecond instead of time.Second.
defer cnx.Close()
Close the connection after the end of processing. A keyword in the GO defer language is used here to predict the instructions to be performed when you exit the func and GO will take over the instruction(s).
.\ClientGoDemo01.exe -action TABLE -Multiplicand 5 -Multiplier 10
This command, after compiling both client and server parts. After starting the server part and it is listening, which is executed in command line whatever shell. Lets us ask the server to return the 5 multiplication table, from 0 to 10 (all our childhood multiplication tables!!). The output result will be:
Client started Table de multiplation de 5 X 10. 5 X 0 = 0 5 X 1 = 5 5 X 2 = 10 5 X 3 = 15 5 X 4 = 20 5 X 5 = 25 5 X 6 = 30 5 X 7 = 35 5 X 8 = 40 5 X 9 = 45 5 X 10 = 50 Client ended.
If you need help on the command line just type the following command:
.\ClientGoDemo01.exe --help
Here we have just seen:
Here, a basic example of setting up a gRPC server and its consumption through a client Using the IDL for the buffers protocol to generate the service, the messages, the service contract.
Project Source on GitHub
Here are some references on the subject gRPC and Protocol Buffers from Google.