This is the 15th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

We have used RPC to implement simple services before, now let’s try gRPC instead.

The installation

Under our project root, execute the Go language gRPC library installation command from the command line as follows:

$go get -u google.golang.org/[email protected]

The sample

Modify hello.proto file, add HelloService interface:

syntax = "proto3";

package proto;

message String {
    string value = 1;
}

service HelloService {
    rpc Hello (String) returns (String);
}
Copy the code

Then use protoc-gen-Go built-in gRPC plug-in to generate gRPC code:

$ protoc --go_out=plugins=grpc:. ./proto/*.proto

Look at the production hello.pb.go file, the gRPC plug-in generates different interfaces for the server and client:

// HelloServiceServer is the server API for HelloService service.
type HelloServiceServer interface {
	Hello(context.Context, *String) (*String, error)
}

// HelloServiceClient is the client API for HelloService service.
type HelloServiceClient interface{ Hello(ctx context.Context, in *String, opts ... grpc.CallOption) (*String, error) }Copy the code

GRPC provides context support for each method call with the context. context parameter.

Based on the HelloServiceServer interface on the server side, we re-implement HelloService:

package main

import (
	"context"
	"google.golang.org/grpc"
	"log"
	"net"
	pb "rpc/proto" // Set the reference alias
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(ctx context.Context, args *pb.String) (*pb.String, error) {
	reply := &pb.String{Value: "hello:" + args.GetValue()}
	return reply, nil
}

func main(a) {
	grpcServer := grpc.NewServer()
	pb.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))

	lis, err := net.Listen("tcp".": 1234")
	iferr ! =nil {
		log.Fatal(err)
	}

	grpcServer.Serve(lis)
}
Copy the code

First by GRPC. NewServer () constructs a GRPC service object, and then through the GRPC plug-in generated RegisterHelloServiceServer function we implement HelloServiceImpl service registration. GRPC service is then provided on a listening port via grpcServer.serve (LIS).

Client link gRPC service:

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"log"
	pb "rpc/proto" // Set the reference alias
)

func main(a) {
	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
	iferr ! =nil {
		log.Fatal("dialing err:", err)
	}
	defer conn.Close()

	client := pb.NewHelloServiceClient(conn)
	reply, err := client.Hello(context.Background(), &pb.String{Value: "wekenw"})
	iferr ! =nil {
		log.Fatal(err)
	}
	fmt.Println(reply.GetValue())
}
Copy the code

Grpc. Dial is responsible for establishing a link to the GRPC service, and the NewHelloServiceClient function constructs the HelloServiceClient object based on the established link. The returned client is actually a HelloServiceClient interface object. The methods defined by the interface can call the methods provided by the corresponding gRPC service on the server side.

Start the server. Start the client. The client execution result is as follows:

$ go run client.go
hello:wekenw
Copy the code

The above is the Unary RPC call mode of GRPC. There are three other ways, and we’ll introduce each of them.

Server-side Streaming RPC: Streaming RPC on the Server

Server-side streaming RPC refers to one-way flow. The Server refers to Stream, and the Client refers to unary RPC requests.

In simple terms, the client initiates a common RPC request, the server sends the data set several times through streaming response, and the client Recv receives the data set.

Proto :

syntax = "proto3";

package proto;

message String {
    string value = 1;
}

service HelloService {
    rpc Hello (String) returns (stream String){};
}
Copy the code

Server:

package main

import (
	"google.golang.org/grpc"
	"log"
	"net"
	pb "rpc/proto" // Set the reference alias
	"strconv"
)

// HelloServiceImpl defines our service
type HelloServiceImpl struct{}

// Implement the Hello method
func (p *HelloServiceImpl) Hello(req *pb.String, srv pb.HelloService_HelloServer) error {
	for n := 0; n < 5; n++ {
		// Send a message to the stream. By default, the maximum length of each send message is' math.maxint32 'bytes
		err := srv.Send(&pb.String{
			Value: req.Value + strconv.Itoa(n),
		})
		iferr ! =nil {
			return err
		}
	}
	return nil
}

func main(a) {
	// Create a gRPC server instance
	grpcServer := grpc.NewServer()
	// Register our service on the gRPC server
	pb.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))

	lis, err := net.Listen("tcp".": 1234")
	iferr ! =nil {
		log.Fatal(err)
	}
	log.Println(" net.Listing...")
	// Use the server Serve() method and our port information area to block and wait until the process is killed or Stop() is called
	err = grpcServer.Serve(lis)
	iferr ! =nil {
		log.Fatalf("grpcServer.Serve err: %v", err)
	}
}
Copy the code

The parameters and return values of Hello are defined in the.pb.go file generated when proto is compiled, so we just need to implement it.

Server side, mainly pay attention to the stream.Send method, through reading the source code, can be learned is protoc in the generation, according to the definition of the generation of a variety of standard interface methods. Finally, the internal SendMsg method is unified, which involves the following process:

  • Message body (object) serialization.
  • Compressing the serialized message body.
  • Adds 5 bytes of header (flag bit) to the body of the message being transmitted.
  • Determines whether the total length of the compressed + serialized message body is greater than the default maxSendMessageSize (default value is math.maxint32) or if it is greater, an error is displayed.
  • A data set written to a stream.

Client:

package main

import (
	"context"
	"google.golang.org/grpc"
	"io"
	"log"
	pb "rpc/proto" // Set the reference alias
)

SayHello calls the Hello method on the server
func SayHello(client pb.HelloServiceClient, r *pb.String) error {
	stream, _ := client.Hello(context.Background(), r)
	for {
		resp, err := stream.Recv()
		if err == io.EOF {
			break
		}
		iferr ! =nil {
			return err
		}

		log.Printf("resp: %v", resp)
	}

	return nil
}


func main(a) {
	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
	iferr ! =nil {
		log.Fatal("dialing err:", err)
	}
	defer conn.Close()

	// Establish a gRPC connection
	client := pb.NewHelloServiceClient(conn)

	// Create a send structure
	req := pb.String{
		Value: "stream server grpc ",
	}
	SayHello(client, &req)
}
Copy the code

On the Client side, pay attention to the stream.recv () method, which encapsulates the clientstream.recvmsg method, which reads the entire gRPC message body from the stream.

  • RecvMsg is blocked waiting.

  • RecvMsg Returns io.eof when the stream succeeds/terminates (Close is called).

  • RecvMsg When any errors occur in the stream, the stream is aborted and the error message contains the RPC error code. The following error can occur in RecvMsg, for example:

    • IO. EOF, IO. ErrUnexpectedEOF
    • transport.ConnectionError
    • Google.golang.org/grpc/codes (gRPC predefined error code)

Note that the default value of MaxReceiveMessageSize is 1024 1024 4. You can adjust the value based on special requirements.

Start the server. Start the client. The result is as follows:

$ go run server.go
2021/11/16 21:57:18  net.Listing...
Copy the code
$ go run client.go
2021/11/16 21:57:31 resp: value:"stream server grpc 0"
2021/11/16 21:57:31 resp: value:"stream server grpc 1"
2021/11/16 21:57:31 resp: value:"stream server grpc 2"
2021/11/16 21:57:31 resp: value:"stream server grpc 3"
2021/11/16 21:57:31 resp: value:"stream server grpc 4"

Copy the code

Client streaming RPC, bidirectional streaming RPC, unfinished, to be continued…