RPC

Those unfamiliar with RPC may struggle with its relationship to TCP, HTTP, and so on. The latter is a protocol in network transmission, and RPC is a design and implementation framework, communication protocol is only a part of it, RPC not only to solve the problem of protocol communication, but also serialization and deserialization, as well as message notification.

A complete RPC architecture contains four core components, namely Client,Server,ClientOptions and ServerOptions. This option is what RPC needs to be designed and implemented.

  • Client: The caller of a service.

  • Server: The real service provider.

  • ClientOption: Socket management, serialization of network incoming and outgoing packets.

  • ServerOption: Socket management, alerting the Server layer to RPC method calls, and serialization of network packets.

The logic diagram of RPC is as follows

What is the gRPC

GRPC is a kind of RPC. It uses Protocol Buffer(Protobuf for short) as the serialization format. Protocol Buffer is a serialization framework from Google, which is lighter and more efficient than Json. Brings features such as bidirectional streaming, flow control, header compression, multiplexing requests over a single TCP connection, and more. These features allow it to perform better on mobile devices, saving power and space. With protoc you can use the proto file to help us generate the option layer code above.

In gRPC, a client application can call a method directly on a server application on another computer as if it were a local object, making it easier for you to create distributed applications and services.

The call model of gRPC is as follows

Applicable scenario

  • Distributed scenarios: gRPC is designed for low latency and high throughput communications, making it ideal for lightweight microservices where efficiency is critical.
  • Point-to-point real-time communication: gRPC provides excellent support for two-way streaming media and can push messages in real time without polling.
  • Multilingual hybrid Development: Support for mainstream development languages makes gRPC an ideal choice for multilingual development environments.
  • Network constrained environment: Serialize gRPC messages using Protobuf, a lightweight message format. The gRPC message is always smaller than the equivalent JSON message.

Four ways to call

Before learning gRPC, let’s introduce the client and server in RPC. In RPC, the server will open the service for the client to call, and each RPC call is a process that the client sends a request to the server to obtain the corresponding process. The intermediate process is encapsulated and looks like a local call. An RPC call is also a communication process.

RPC calls can be divided into single RPC, server – side streaming RPC, client – side streaming RPC and bidirectional streaming PRC, depending on whether the two ends interact with each other streaming. In order to facilitate you to understand the application scenarios of the four GRPC calls, here is an example. Suppose you are xiaochao and have a girlfriend named Tingting. Each emotion of Tingting represents a micro service, and each conversation between you can be interpreted as a CALL from PRC. The request package is HelloRequest and the response is HelloReply.

1. A single RPC

When you are waiting for Tingting to go back for dinner and Tingting is working overtime, the RPC call between you might look like this:

Xiao Chao: Coming back for dinner

Tingting: Still working overtime

This is called one-way RPC, where the client sends a request to the server and gets a response from the server, just like a normal function call.

  • The client layer calls the SayHello interface to serialize the HelloRequest package
  • Client Option sends serialized data to the server
  • Server Option received an RPC request. Procedure
  • The RPC request is returned to the server, which processes the request and gives the result to Server Option
  • Server Option serializes the HelloReply and sends it to the client
  • Client Option does the deserialization and returns it to the client layer
2. Streaming RPC on the server

When you lose a match, send a message to Tingting:

Xiao Chao: I lost today

Tingting: No, it’s just a competition

Tingting: Take you out for a nice dinner tonight

This is called server-side streaming RPC, where a client sends a request to a server and gets a data stream to read a series of messages. The client reads from the returned data stream until there are no more messages.

  • The client layer calls the SayHello interface to serialize the HelloRequest package
  • Client Option sends serialized data to the server
  • Server Option received an RPC request. Procedure
  • The RPC request is returned to the server, which processes it and streams data to Server Option
  • Server Option serializes the HelloReply and sends it to the client
  • Client Option does the deserialization and returns it to the client layer
3. Stream RPC on the client

When you upset Tingting:

Xiao Chao: What’s up, honey

Xiao Chao: Don’t be angry, take you to eat delicious

Tingting: roll

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 those messages and return a reply,

  • The client layer calls the SayHello interface to serialize the HelloRequest package
  • Client Option sends the serialized data stream to the server
  • Server Option received an RPC request. Procedure
  • The RPC request is returned to the server, which processes the request and gives the result to Server Option
  • Server Option serializes the HelloReply and sends it to the client
  • Client Option does the deserialization and returns it to the client layer
4. Bidirectional flow RPC

When you’re done with Tingting:

Xiao Chao: I watched a very nice video today

Tingting: What video

Xiao Chao: I’ll send it to you

Tingting: Is that good?

Two-way flow RPC, in which both sides can send a series of messages through a separate 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

This diagram does not make the process introduction, readers can try to see whether the diagram can understand the process, I believe that the understanding of client flow RPC and server flow RPC two ways, here must be understandable.

GPRC code implementation

GRPC uses Protobuf as its serialization format, which is lighter and more efficient than Json. Like Json, it is language – and platform-independent and extensible. About developers.google.com/protocol-bu Protobuf use please refer to the website address… .

Below we implement the Go language version of the four gRPC call way.

1. One-way RPC implementation
Write the proto
/ / proto3 standards
syntax = "proto3";

/ / package name
package helloworld;

// Define the RPC interface
service Greets {
  rpc SayHello (HelloRequest) returns (HelloReply) {}}//HelloReply protocol content
message HelloReply {
  string name = 1;
  string message= 2; } / /HelloRequestProtocol message HelloRequest{
  string name = 1;
  string message = 2;
}
Copy the code
  1. Greet indicates the name of the Greet class that defines the RPC service. RPC SayHello (HelloRequest) returns (HelloReply) {} means to define the RPC method SayHello, pass HelloRequest, and return HelloReply
  2. Go to proto and run protoc -i. –go_out=plugins= GRPC:.. / helloWorld.proto in. The helloWorld.pb. go file is generated in the directory
Write the server
type Server struct{}// Implement SayHello interface
func (s *Server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Println(in.Name, in.Message)
	return &pb.HelloReply{Name: "Tingting", Message: "Not coming back."}, nil
}

func main(a) {
	// Protocol type and IP address, port
	lis, err := net.Listen("tcp".": 8002")
	iferr ! =nil {
		fmt.Println(err)
		return
	}

	// Define an RPC server
	server := grpc.NewServer()
	// Register the service, similar to register the SayHello interface
	pb.RegisterGreetsServer(server, &Server{})
	// Perform mapping binding
	reflection.Register(server)

	// Start the service
	err = server.Serve(lis)
	iferr ! =nil {
		fmt.Println(err)
		return}}Copy the code
  1. Pb is the alias of the file generated by proto

  2. Define the server structure as the structure for RPC calls. This structure must implement the SayHello interface

  3. listen -> grpc.NewServer() -> pb.RegisterGreetsServer(server, &Server{}) -> s.Serve(lis)

Write the client
func main(a) {
	// Create a GRPC connection
	conn, err := grpc.Dial("localhost:8002", grpc.WithInsecure())
	iferr ! =nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()

	// Create an RPC client
	client := pb.NewGreetsClient(conn)
	// Set the timeout period
	_, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// Call the method
	reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "Little super", Message: "Coming back for dinner?"})
	iferr ! =nil {
		log.Fatalf("couldn not greet: %v", err)
	}
	log.Println(reply.Name, reply.Message)
}
Copy the code
  1. Grpc. Dial(“localhost:8002”, grpc.withinsecure ()) connects to the server, and grpc.withinsecure () cancels plaintext detection
  2. Context.withtimeout (context.background (), time.second) Sets the timeout period
  3. C := pb.NewGreetsClient(conn) Creates the client for RPC calls
  4. C. sayhello (context.background (), &pb.HelloRequest{Name: Name}) makes an RPC call
Abstract interface

In fact, this is to implement this interface, because both sides are single invocation, so the call and implementation of the interface is this

type GreetsClient interface{ SayHello(ctx context.Context, in *HelloRequest, opts ... grpc.CallOption) (*HelloReply, error) }Copy the code
2. RPC on the server
Write the proto
/ / proto3 standards
syntax = "proto3";

/ / package name
package helloworld;

// Define the RPC interface

service Greet{
  rpc SayHello (HelloRequest) returns(stream HelloReply) {}}//HelloReply protocol content
message HelloReply {
  string name = 1;
  string message= 2; } / /HelloRequestProtocol message HelloRequest{
  string name = 1;
  string message = 2;
}
Copy the code

Compared to a single RPC call, there is an extra stream in HelloRequest because it is a client stream

Write the server
type Server struct{}// Implement the RPC interface
func (*Server) SayHello(request *pb.HelloRequest, server pb.Greet_SayHelloServer) error {
	fmt.Println(request)
	var err error
	for i := 0; i < 2; i++ {
		if i == 0 {
			err = server.Send(&pb.HelloReply{Name: "Little super", Message: "It's okay. It's just a race."})}else {
			err = server.Send(&pb.HelloReply{Name: "Little super", Message: "Take you out for a nice dinner tonight."})}iferr ! =nil {
			fmt.Println(err)
			return err
		}
	}
	return nil
}

func main(a) {
	// Protocol type and IP address, port
	listen, err := net.Listen("tcp".": 8002")
	iferr ! =nil {
		fmt.Println(err)
		return
	}

	// Define an RPC server
	s := grpc.NewServer()
	// Register the service, similar to register the SayHello interface
	pb.RegisterGreetServer(s, &Server{})
	// Perform mapping binding
	reflection.Register(s)

	// Start the service
	err = s.Serve(listen)
	iferr ! =nil {
		fmt.Println(err)
	}
}
Copy the code
Write the client

The client sends a stream. Different from the single RPC method, the client obtains a stream transfer object greetClient through RPC calls. The stream transfer object can be used to continuously send data to the peer end

func main(a) {
	// Create a GRPC connection
	grpcConn, err := grpc.Dial("127.0.0.1"+": 8002", grpc.WithInsecure())
	iferr ! =nil {
		fmt.Println(err)
		return
	}

	// Create a GRPC client
	client := pb.NewGreetClient(grpcConn)
	// Set the timeout period
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Call the RPC method to get the stream interface
	res, err := client.SayHello(ctx, &pb.HelloRequest{Name: "Little super", Message: "Lost the game today."})
	iferr ! =nil {
		fmt.Println(err)
		return
	}

	// Loop to receive data
	for {
		recv, err := res.Recv()
		iferr ! =nil {
			fmt.Println(err)
			break
		}
		fmt.Println(recv)
	}
}
Copy the code
Abstract interface

Interface to be implemented by the server

// GreetsServer is the server API for Greets service.
type GreetsServer interface {
   SayHello(Greets_SayHelloServer) error
}
Copy the code

The interface that the client invokes

type GreetsClient interface{ SayHello(ctx context.Context, opts ... grpc.CallOption) (Greets_SayHelloClient, error) }Copy the code
3. RPC on the server
Write the proto
/ / proto3 standards
syntax = "proto3";

/ / package name
package helloworld;

// Define the RPC interface
service Greets{
  rpc SayHello (stream HelloRequest) returns (HelloReply) {}}//HelloReply protocol content
message HelloReply {
  string name = 1;
  string message= 2; } / /HelloRequestProtocol message HelloRequest{
  string name = 1;
  string message = 2;
}
Copy the code
Authoring server
type Server struct{}

// Implement the RPC method until the peer call CloseAndRecv reads the EOF
func (*Server) SayHello(in pb.Greets_SayHelloServer) error {
	for {
		recv, err := in.Recv()
		// Send the response after receiving the data
		if err == io.EOF {
			err := in.SendAndClose(&pb.HelloReply{Name: "Tingting", Message: "Roll"})
			iferr ! =nil {
				return err
			}
			return nil
		} else iferr ! =nil {
			return err
		}
		fmt.Println(recv)
	}
}

func main(a) {
	// Bind protocol, IP, and port
	lis, err := net.Listen("tcp".": 8002")
	iferr ! =nil {
		fmt.Println("failed to listen: %v", err)
		return
	}

	// Create a GRPC service object
	server := grpc.NewServer()
	// Register the RPC service
	pb.RegisterGreetsServer(server, &Server{})
	// Register server reflection
	reflection.Register(server)

	// Start the server
	err = server.Serve(lis)
	iferr ! =nil {
		fmt.Println(err)
		return}}Copy the code
Writing a client
func main(a) {
	// Create a GRPC connection
	grpcConn, err := grpc.Dial("127.0.0.1"+": 8002", grpc.WithInsecure())
	iferr ! =nil {
		fmt.Println(err)
		return
	}

	// Create a GRPC client
	client := pb.NewGreetsClient(grpcConn)

	// Set the timeout period
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Call the RPC method to get a client to loop data
	greetClient, err := client.SayHello(ctx)

	iferr ! =nil {
		fmt.Println("sayHello error")
		fmt.Println(err)
		return
	}

	maxCount := 2
	curCount := 0

	// Send in a loop
	// If CloseAndRecv() is called, the server will read the EOF, and the server can determine whether the client has sent the data according to the EOF
	for {
		if curCount == 0 {
			err = greetClient.Send(&pb.HelloRequest{Name: "Little super", Message: "What's up, baby?"})}else {
			err = greetClient.Send(&pb.HelloRequest{Name: "Little super", Message: "Don't be mad. Let's get you something to eat."})}iferr ! =nil {
			fmt.Println("send error")
			fmt.Println(err)
			return
		}
		curCount += 1
		if curCount >= maxCount {
			res, err := greetClient.CloseAndRecv()
			iferr ! =nil {
				fmt.Println(err)
				break
			}
			fmt.Println(res)
			break}}}Copy the code
Abstract interface

Client interface

type GreetsClient interface { SayHello(ctx context.Context, opts ... grpc.CallOption) (Greets_SayHelloClient, error) }Copy the code

Server interface

// GreetsServer is the server API for Greets service.
type GreetsServer interface {
   SayHello(Greets_SayHelloServer) error
}
Copy the code

Two-way flow of RPC

Two-way flow RPC to the reader to practice it, I believe that understand the single RPC, client flow RPC, server flow RPC three transmission methods, write two-way flow RPC should not have any problem.

Implementation summary

In fact, understand the single RPC, server-side streaming RPC, client streaming RPC, two-way flow PRC four GRPC application scenarios, it is very easy to achieve

  1. Select a gRPC service based on the application scenario
  2. Write proto file and use protoc to generate.pb.go file
  3. Server implementation interface -> LISTEN -> grpc.newServer () -> pb.RegisterGreetsServer(server, & server {}) -> s.sever (lis)
  4. Client grpc.Dial->pb.NewGreetsClient-> Context. WithTimeout->client.SayHello -> Loop reads data in case of stream transmission

Welcome to pay attention to my official account, check the super super follow-up content updates!