Welcome to my GitHub
Github.com/zq2599/blog…
Content: all original article classification summary and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc.;
GRPC learning series of articles links
- Deploy and set GO on CentOS7
- Prepare gRPC development environment for GO
- The first GO version of gRPC development
- Actual combat four kinds of service methods
- GRPC – Gateway of actual combat
- GRPC – Gateway integration swagger
This paper gives an overview of
- This article “gRPC learning” series of the fourth article, the previous article we experience the most simple gRPC development, write the client to call the server, but this is only the most simple one, in solving the actual problem is far from enough;
- In fact, gRPC allows you to define the following four classes of service methods (described below from doc.oschina.net/grpc) :
- Monomial RPC, where a client sends a request to a server and gets a response from the server, just like a normal function call (as in the previous article);
- Server-side streaming RPC, in which a client sends a request to a server to retrieve a data stream that can be used to read a series of messages. The client reads from the returned data stream until there are no more messages;
- Client-side streaming RPC, in which a client writes and sends a series of messages to a server using a provided data stream. Once the client has finished writing messages, it waits for the server to read them and return a reply.
- Two-way streaming RPC, in which both sides can send a series of messages through a single read-write data stream. The two data flow operations are independent of each other, so the client and server can read and write in any order they wish. For example, the server can wait for all client messages before writing a reply, or it can read one message and then write another, or some other combination of read and write. The order of messages in each data stream is maintained.
- The content of this paper is to code and implement the above four types of service methods, and write the client code to call. The whole development process is shown in the figure below:
Download the source code
- The source code for this article can be downloaded on GitHub, with the address and link information shown in the following table (github.com/zq2599/blog…
The name of the | link | note |
---|---|---|
Project home page | Github.com/zq2599/blog… | The project’s home page on GitHub |
Git repository address (HTTPS) | Github.com/zq2599/blog… | The project source warehouse address, HTTPS protocol |
Git repository address (SSH) | [email protected]:zq2599/blog_demos.git | The project source warehouse address, SSH protocol |
- The Git project has multiple folders. The application for this chapter is in the Go-source folder, as shown in the red box below:
- There are several sub-folders in go-Source. The source code for this piece is in grpcStream, as shown in the red box below:
State the files and directories in advance
- $GOPATH/ SRC grpcStream = $GOPATH/ SRC
[golang @ centos7 SRC] $tree grpcstream grpcstream / ├ ─ ─ client │ └ ─ ─ client. Go ├ ─ ─ grpcstream. Pb. Go ├ ─ ─ grpcstream. The proto └ ─ ─ server └ ─ ─ server. GoCopy the code
- The preparatory work is complete, and then the development begins in earnest;
Write proto files
- The proto file is named grpcstream.proto, located at $GOPATH/ SRC /grpcstream, and contains the following contents (some notes will be pointed out later) :
// Protocol type
syntax = "proto3";
/ / package name
package grpcstream;
// Data structure requested by the server
message SingleRequest {
int32 id = 1;
}
// The data structure of the server response
message SingleResponse {
int32 id = 1;
string name = 2;
}
// The defined service name
service IGrpcStremService {
// Single RPC: single request, single response
rpc SingleReqSingleResp (SingleRequest) returns (SingleResponse);
// Server-side streaming: a single request, a collection of responses
rpc SingleReqMultiResp (SingleRequest) returns (stream SingleResponse);
// Client stream: a collection of requests, a single response
rpc MultiReqSingleResp (stream SingleRequest) returns (SingleResponse);
// Two-way streaming: set request, set response
rpc MultiReqMultiResp (stream SingleRequest) returns (stream SingleResponse);
}
Copy the code
- The grpcstream.proto file has the following caveats:
- The SingleReqSingleResp method is very simple and, like the demo in the previous article, the input parameter is a data structure and the server returns a data structure;
- The SingleReqSingleResp method is a server-side stream type characterized by a return value decorated with stream;
- The MultiReqSingleResp method is a client stream type, characterized by an input parameter decorated with a stream.
- The MultiReqMultiResp method is bi-directional, characterized by a stream modifier for both the input and return values;
- If a client wants to channel continuous data to a server, it will use the stream modifier in the channel location. There are two locations: the first is the input parameter to the server, and the second is the return value from the server.
Generate go source code according to Proto
- In the directory where grpcstream.proto is located, execute the following command:
protoc --go_out=plugins=grpc:. grpcstream.proto
Copy the code
- If grpcstream.proto has no syntax errors, the file grpcstream.pb.go will be generated in the current directory, which is automatically generated by the protoc-gen-go tool. The generated code will be used in both server and client development.
- For the server, the most important interface in GrpcStream.pb. go is the IGrpcStremServiceServer interface. The server needs to implement all the methods of this interface as business logic. The interface is defined as follows:
type IGrpcStremServiceServer interface {
// Single flow: single request, single response
SingleReqSingleResp(context.Context, *SingleRequest) (*SingleResponse, error)
// Server-side streaming: a single request, a collection of responses
SingleReqMultiResp(*SingleRequest, IGrpcStremService_SingleReqMultiRespServer) error
// Client stream: a collection of requests, a single response
MultiReqSingleResp(IGrpcStremService_MultiReqSingleRespServer) error
// Two-way streaming: set request, set response
MultiReqMultiResp(IGrpcStremService_MultiReqMultiRespServer) error
}
Copy the code
- For clients, the most important thing in grpcStream.pb. go is the IGrpcStremServiceClient interface, as shown below, which means what remote calls the client can make:
type IGrpcStremServiceClient interface {
// Single flow: single request, single responseSingleReqSingleResp(ctx context.Context, in *SingleRequest, opts ... grpc.CallOption) (*SingleResponse, error)// Server-side streaming: a single request, a collection of responsesSingleReqMultiResp(ctx context.Context, in *SingleRequest, opts ... grpc.CallOption) (IGrpcStremService_SingleReqMultiRespClient, error)// Client stream: a collection of requests, a single responseMultiReqSingleResp(ctx context.Context, opts ... grpc.CallOption) (IGrpcStremService_MultiReqSingleRespClient, error)// Two-way streaming: set request, set responseMultiReqMultiResp(ctx context.Context, opts ... grpc.CallOption) (IGrpcStremService_MultiReqMultiRespClient, error) }Copy the code
Write the server-side code server.go and start it
- Create a new folder server under $GOPATH/ SRC /grpcstream, and create a new folder server.go under this folder, with the following contents (a few points to note later) :
package main
import (
"context"
"google.golang.org/grpc"
pb "grpcstream"
"io"
"log"
"net"
"strconv"
)
// Constant: listening port
const (
port = ": 50051"
)
// Define a structure to be used as an input parameter when calling the registration API.
// This structure takes the methods defined in PROto, which contains the business code
// The business code is executed when the remote call is made
type server struct {
// Pb. go automatically generated, is an empty structure
pb.UnimplementedIGrpcStremServiceServer
}
// Single flow: single request, single response
func (s *server) SingleReqSingleResp(ctx context.Context, req *pb.SingleRequest) (*pb.SingleResponse, error) {
id := req.GetId()
// Prints the request parameters
log.Println("1. Receipt of a request :", id)
// instantiate the structure SingleResponse as the return value
return &pb.SingleResponse{Id: id, Name: "1. name-" + strconv.Itoa(int(id))}, nil
}
// Server-side streaming: a single request, a collection of responses
func (s *server) SingleReqMultiResp(req *pb.SingleRequest, stream pb.IGrpcStremService_SingleReqMultiRespServer) error {
// Get the request parameters
id := req.GetId()
// Prints the request parameters
log.Println("2. Request received :", id)
// Return multiple records
for i := 0; i < 10; i++ {
stream.Send(&pb.SingleResponse{Id: int32(i), Name: "2. name-" + strconv.Itoa(i)})
}
return nil
}
// Client stream: a collection of requests, a single response
func (s *server) MultiReqSingleResp(reqStream pb.IGrpcStremService_MultiReqSingleRespServer) error {
var addVal int32 = 0
// Receive streaming requests in the for loop
for {
// Accept one record at a time
singleRequest, err := reqStream.Recv()
// does not equal IO.EOF means this is a valid record
if err == io.EOF {
log.Println("3. The client has finished sending")
break
} else iferr ! =nil {
log.Fatalln("3. An exception occurs when receiving", err)
break
} else {
log.Println("3. Request received :", singleRequest.GetId())
// Call SendAndClose to return data and end the call
addVal += singleRequest.GetId()
}
}
return reqStream.SendAndClose(&pb.SingleResponse{Id: addVal, Name: "3. name-" + strconv.Itoa(int(addVal))})
}
// Two-way streaming: set request, set response
func (s *server) MultiReqMultiResp(reqStream pb.IGrpcStremService_MultiReqMultiRespServer) error {
// Simple processing, return a response for each record received
for {
singleRequest, err := reqStream.Recv()
// does not equal IO.EOS means this is a valid record
if err == io.EOF {
log.Println("4. Acceptance completed")
return nil
} else iferr ! =nil {
log.Fatalln("4. An exception occurs when receiving", err)
return err
} else {
log.Println("4. Data received", singleRequest.GetId())
id := singleRequest.GetId()
if sendErr := reqStream.Send(&pb.SingleResponse{Id: id, Name: "4. name-" + strconv.Itoa(int(id))}); sendErr ! =nil {
log.Println("4. Abnormal data returned", sendErr)
return sendErr
}
}
}
}
func main(a) {
// Protocol and port to listen on
lis, err := net.Listen("tcp", port)
iferr ! =nil {
log.Fatalf("failed to listen: %v", err)
}
// Instantiate the gRPC Server structure
s := grpc.NewServer()
// Service registration
pb.RegisterIGrpcStremServiceServer(s, &server{})
log.Println("Start listening, waiting for remote call...")
iferr := s.Serve(lis); err ! =nil {
log.Fatalf("failed to serve: %v", err)
}
}
Copy the code
- The server.go file has the following caveats:
- The SingleReqMultiResp method receives a request parameter from the client and sends multiple responses to the client.
- The MultiReqSingleResp method receives multiple bytes from the client, so it calls reqstream.recv () repeatedly in the for loop until it receives io.eof from the client. The client code later shows how to do this;
- The MultiReqMultiResp method continues to receive data from the client, and continues to send data to the client. The order must be considered, otherwise an exception will occur (such as an infinite loop). If the client is also waiting for data through the for loop, both parties are waiting for data and cannot terminate the program. The client code shows how to do this later.
- Run the go Run server.go command in the directory where server.go resides.
[golang@centos7 server]$go run server.go 2020/12/13 21:29:19Copy the code
- At this point, the server of gRPC has been started and can respond to remote calls. Next, the client code is developed.
Write the client code client.go
- Open another console;
- Create a new client folder under $GOPATH/ SRC /grpcstream and create client.go under this folder with the following contents (a few notes will be pointed out later) :
package main
import (
"context"
"google.golang.org/grpc"
"io"
"log"
"time"
pb "grpcstream"
)
const (
address = "localhost:50051"
defaultId = "666"
)
func main(a) {
// Connect to the server remotely
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
iferr ! =nil {
log.Fatalf("did not connect: %v", err)
}
// Close the remote connection after the main method is complete
defer conn.Close()
// Instantiate the data structure
client := pb.NewIGrpcStremServiceClient(conn)
// Set timeout
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
log.Println("Test single request response, one to one.")
singleReqSingleResp(ctx, client)
log.Println("Test server-side streaming response, one-to-many.")
singleReqMultiResp(ctx, client)
log.Println("Testing client streaming requests, many-to-one.")
multiReqSingleResp(ctx, client)
log.Println("Test two-way streaming request response, many-to-many.")
multiReqMultiResp(ctx, client)
log.Println("Test completed")}func singleReqSingleResp(ctx context.Context, client pb.IGrpcStremServiceClient) error {
// Remote call
r, err := client.SingleReqSingleResp(ctx, &pb.SingleRequest{Id: 101})
iferr ! =nil {
log.Fatalf("1. Remote call exception: %v", err)
return err
}
// Print the information returned by the server
log.Printf("response, id : %d, name : %s", r.GetId(), r.GetName())
return nil
}
func singleReqMultiResp(ctx context.Context, client pb.IGrpcStremServiceClient) error {
// Remote call
recvStream, err := client.SingleReqMultiResp(ctx, &pb.SingleRequest{Id: 201})
iferr ! =nil {
log.Fatalf("2. Remote call exception: %v", err)
return err
}
for {
singleResponse, err := recvStream.Recv()
if err == io.EOF {
log.Printf("2. Data acquisition completed")
break
}
log.Printf(Id: %d, name: %s", singleResponse.GetId(), singleResponse.GetName())
}
return nil
}
func multiReqSingleResp(ctx context.Context, client pb.IGrpcStremServiceClient) error {
// Remote call
sendStream, err := client.MultiReqSingleResp(ctx)
iferr ! =nil {
log.Fatalf("3. Remote call exception: %v", err)
return err
}
// Send multiple records to the server
for i:=0; i<10; i++ {
if err = sendStream.Send(&pb.SingleRequest{Id: int32(300+i)}); err! =nil {
log.Fatalf("3. Failed to send data through stream: %v", err)
return err
}
}
singleResponse, err := sendStream.CloseAndRecv()
iferr ! =nil {
log.Fatalf("3. Abnormal server response: %v", err)
return err
}
// Print the information returned by the server
log.Printf("response, id : %d, name : %s", singleResponse.GetId(), singleResponse.GetName())
return nil
}
func multiReqMultiResp(ctx context.Context, client pb.IGrpcStremServiceClient) error {
// Remote call
intOutStream, err := client.MultiReqMultiResp(ctx)
iferr ! =nil {
log.Fatalf("4. Remote call exception: %v", err)
return err
}
// Send multiple records to the server
for i:=0; i<10; i++ {
if err = intOutStream.Send(&pb.SingleRequest{Id: int32(400+i)}); err! =nil {
log.Fatalf("4. Failed to send data through stream: %v", err)
return err
}
}
// The server continues to receive until it receives io.eof
// Therefore, the IO.EOF must be sent to the server to let the server know that the sending is finished (important)
intOutStream.CloseSend()
// Receives data from the server
for {
singleResponse, err := intOutStream.Recv()
if err == io.EOF {
log.Printf("4. Data acquisition completed")
break
} else iferr ! =nil {
log.Fatalf("4. Abnormal receiving server data: %v", err)
break
}
log.Printf("4. Server response id: %d, name: %s", singleResponse.GetId(), singleResponse.GetName())
}
return nil
}
Copy the code
- The client.go file has the following caveats:
- The singleReqMultiResp method receives multiple records from the server and calls recvStream.Recv in the for loop to receive all data.
- The multiReqSingleResp method sends multiple pieces of data to the server. Since the server is waiting for io.EOF as an end flag, sendStream.CloseAndRecv sends io.EOF and returns the value.
- The multiReqMultiResp method is continuously sending data to the server and receiving data from the server. After the data is sent, the intOutstream. CloseSend method must be called to send IO.
- In the main method, four classes of service methods are called in turn.
Execution client
- After the coding is complete, go run client.go is executed in the directory where client.go resides, and a remote call is immediately made to the server.
[golang@centos7 client]$go run client.go 2020/12/13 21:39:35 Response, id: 101, name: 1. name-101 2020/12/13 21:39:35 Testing server streaming response, one-to-many 2020/12/13 21:39:35 2. Id: 0, name: 2. Name-0 2020/12/13 21:39:35 2. Id: 1, name: 2. Name -1 2020/12/13 21:39:35 2. Id: 2, name: 2. Name -2 2020/12/13 21:39:35 2. Id :3, name: 2. Name -3 2020/12/13 21:39:35 2. Id: 4, name: 2. Name -4 2020/12/13 21:39:35 2. Id: 5, name: 2. Name -5 2020/12/13 21:39:35 2. Id: 6, name: 2. Name -6 2020/12/13 21:39:35 2. Id: 7, name: 2. Name -7 2020/12/13 21:39:35 2. Id: 8, name: 2. Name -8 2020/12/13 21:39:35 2. Id: 9, name: 2. Name -9 2020/12/13 21:39:35 2. 2020/12/13 21:39:35 Response, ID: 3045, name: 3. Name-3045 2020/12/13 21:39:35 Test two-way streaming request response, many-to-many 2020/12/13 21:39:35 4. Id: 400, name: 4. name-400 2020/12/13 21:39:35 4. Id: 401, name: 4. name-401 2020/12/13 21:39:35 4. Id: 402, name: 4. name-402 2020/12/13 21:39:35 4. Id: 403, name: 4. Name-403 2020/12/13 21:39:35 4. Id: 404, name: 4. name-404 2020/12/13 21:39:35 4. Id: 405, name: 4. name-405 2020/12/13 21:39:35 4. Id: 406, name: 4. name-406 2020/12/13 21:39:35 4. Id: 407, name: 4. name-407 2020/12/13 21:39:35 4. Id: 408, name: 4. name-408 2020/12/13 21:39:35 4. Id: 409, name: 4. name-409 2020/12/13 21:39:35 4. Data is obtained. 2020/12/13 21:39:35 The test is completeCopy the code
- Go to the console on the server side and check the log to see that the business code was executed and the parameters of the remote request were received:
[golang@centos7 server]$go run server.go 2020/12/13 21:29:19 2020/12/13 21:39:35 1. Received request: 101 2020/12/13 21:39:35 2. Received request: 201 2020/12/13 21:39:35 3. Received request: 300 2020/12/13 21:39:35 3. Received request: 301 2020/12/13 21:39:35 3. Received request: 302 2020/12/13 21:39:35 3. Request received: 303 2020/12/13 21:39:35 3. Request received: 304 2020/12/13 21:39:35 3. Request received: 305 2020/12/13 21:39:35 3. Received request: 306 2020/12/13 21:39:35 3. Request received: 307 2020/12/13 21:39:35 3. Request received: 308 2020/12/13 21:39:35 3. Received request: 309 2020/12/13 21:39:35 3. Client send complete 2020/12/13 21:39:35 4. Data received 400 2020/12/13 21:39:35 4. Received data 401 2020/12/13 21:39:35 4. Data received 402 2020/12/13 21:39:35 4. 403 2020/12/13 21:39:35 4. 404 2020/12/13 21:39:35 4. Data received 405 2020/12/13 21:39:35 4. 406 2020/12/13 21:39:35 4. Data received 407 2020/12/13 21:39:35 4. 408 2020/12/13 21:39:35 4. Data received 409 2020/12/13 21:39:35 4. After receivingCopy the code
- So far, the gRPC four types of service method server-side, client development we have tried, these four types of methods can cover most of the business scenario requirements, I hope this article can give you some reference, the next article will continue to learn gRPC rich functions;
You are not alone, Xinchen original accompany all the way
- Java series
- Spring series
- The Docker series
- Kubernetes series
- Database + middleware series
- The conversation series
Welcome to pay attention to the public number: programmer Xin Chen
Wechat search “programmer Xin Chen”, I am Xin Chen, looking forward to enjoying the Java world with you…
Github.com/zq2599/blog…