14、RPC与gRPC

1. RPC basics

1 - getting started with rpc

  • ipc : Inter-Process Communication, inter-process communication, inter-process communication refers to the interaction between the data of two processes
  • rpc : RPC is the abbreviation of remote procedure call, which is a popular communication method between different nodes in a distributed system
    • RPC transport protocol
    • Message serialization and deserialization
      insert image description here

2 - Basic rpc communication

  • server
package main

import (
	"fmt"
	"log"
	"net"
	"net/rpc"
)

// servuce handler
type HelloService struct {
    
    
}

// Hello的逻辑 就是 将对方发送的消息前面添加一个Hello 然后返还给对方
// 由于我们是一个rpc服务, 因此参数上面还是有约束:
//
//	第一个参数是请求
//	第二个参数是响应
//
// 可以类比Http handler
func (p *HelloService) Hello(request string, response *string) error {
    
    
	*response = fmt.Sprintf("hello,%s", request)
	return nil
}

func main() {
    
    
	// 把我们的对象注册成一个rpc的 receiver
	// 其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数
	// 所有注册的方法会放在“HelloService”服务空间之下
	rpc.RegisterName("HelloService", &HelloService{
    
    })

	// 然后我们建立一个唯一的TCP链接
	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
    
    
		log.Fatal("ListenTCP error:", err)
	}

	// 通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。
	// 没Accept一个请求,就创建一个goroutie进行处理
	for {
    
    
		conn, err := listener.Accept()
		if err != nil {
    
    
			log.Fatal("Accept error:", err)
		}

		// 前面都是tcp的知识, 到这个RPC就接管了
		// 因此 你可以认为 rpc 帮我们封装消息到函数调用的这个逻辑
		// 提升了工作效率, 逻辑比较简洁,可以看看他代码
		go rpc.ServeConn(conn)
	}
}

  • client
package main

import (
	"fmt"
	"log"
	"net/rpc"
)

func main() {
    
    
	// 首先是通过rpc.Dial拨号RPC服务, 建立连接
	client, err := rpc.Dial("tcp", "localhost:1234")
	if err != nil {
    
    
		log.Fatal("dialing:", err)
	}

	// 然后通过client.Call调用具体的RPC方法
	// 在调用client.Call时:
	// 		第一个参数是用点号链接的RPC服务名字和方法名字,
	// 		第二个参数是 请求参数
	//      第三个是请求响应, 必须是一个指针, 有底层rpc服务帮你赋值
	var reply string
	err = client.Call("HelloService.Hello", "jack", &reply)
	if err != nil {
    
    
		log.Fatal(err)
	}

	fmt.Println(reply)
}

insert image description here

3 - Interface-based RPC service

  • Optimizations that exist in the rpc method
    • The client call method contains 3 parameters and 2 interface{}. You may not know what to pass in when you use it again. It’s like you wrote an HTTP service. There is no interface document, and it is easy to call errors
    • In other words, how to encapsulate the rpc interface once to limit the parameter type of the interface
// Call invokes the named function, waits for it to complete, and returns its error status.
func (client *Client) Call(serviceMethod string, args interface{
    
    }, reply interface{
    
    }) error {
    
    
	call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
	return call.Error
}
  • interface\service\interface.go
package service

const (
	//统一rpc的注册名
	SERVICE_NAME = "HelloService"
)

type HelloService interface {
    
    
	Hello(request string, response *string) error
}

  • interface\server\main.go : add interface constraints
package main

import (
	"fmt"
	"log"
	"main/interface/service"
	"net"
	"net/rpc"
)

// 通过接口约束HelloService服务
var _ service.HelloService = (*HelloService)(nil)

type HelloService struct {
    
    
}

func (p *HelloService) Hello(request string, response *string) error {
    
    
	*response = fmt.Sprintf("hello,%s", request)
	return nil
}

func main() {
    
    
	rpc.RegisterName(service.SERVICE_NAME, &HelloService{
    
    })
	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
    
    
		log.Fatal("ListenTCP error:", err)
	}
	for {
    
    
		conn, err := listener.Accept()
		if err != nil {
    
    
			log.Fatal("Accept error:", err)
		}
		go rpc.ServeConn(conn)
	}
}

  • interface\client\main.go : add interface constraints
package main

import (
	"fmt"
	"main/interface/service"
	"net/rpc"
)

// 约束客户端
var _ service.HelloService = (*HelloServiceClient)(nil)

func NewHelloServiceClient(network string, address string) (*HelloServiceClient, error) {
    
    
	client, err := rpc.Dial(network, address)
	if err != nil {
    
    
		return nil, err
	}

	return &HelloServiceClient{
    
    
		client: client,
	}, nil
}

type HelloServiceClient struct {
    
    
	client *rpc.Client
}

func (c *HelloServiceClient) Hello(request string, response *string) error {
    
    
	return c.client.Call(fmt.Sprintf("%s.Hello", service.SERVICE_NAME), request, response)
}

func main() {
    
    
	c, err := NewHelloServiceClient("tcp", ":1234")
	if err != nil {
    
    
		panic(err)
	}
	var response string
	if err := c.Hello("tom", &response); err != nil {
    
    
		panic(err)
	}

	fmt.Println(response)
}

Two, rpc encoding

1 - gob encoding

  • What is gob encoding : RPC in the standard library defaults to the gob encoding specific to the Go language. The standard library gob is a "private" encoding and decoding method provided by golang. It is more efficient than json, xml, etc., and is especially suitable for use in the Go language. passing data between programs
  • codec\gob.go
package codec

import (
	"bytes"
	"encoding/gob"
)

func GobEncode(obj interface{
    
    }) ([]byte, error) {
    
    
	buf := bytes.NewBuffer([]byte{
    
    })
	//编码后的结果输出到buf里
	encoder := gob.NewEncoder(buf)
	//编码obj对象
	if err := encoder.Encode(obj); err != nil {
    
    
		return []byte{
    
    }, err
	}
	return buf.Bytes(), nil
}

func GobDecode(data []byte, obj interface{
    
    }) error {
    
    
	decoder := gob.NewDecoder(bytes.NewReader(data))
	return decoder.Decode(obj)
}

  • codec\gob_test.go
package codec

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
)

type TestStruct struct {
    
    
	F1 string
	F2 int
}

func TestGob(t *testing.T) {
    
    
	should := assert.New(t)
	gobBytes, err := GobEncode(&TestStruct{
    
    
		F1: "test_f1",
		F2: 10,
	})

	if should.NoError(err) {
    
    
		fmt.Println(gobBytes)
	}

	obj := TestStruct{
    
    }
	err = GobDecode(gobBytes, &obj)
	if should.NoError(err) {
    
    
		fmt.Println(obj)
	}
}

2 - json on tcp

  • Disadvantages of gob encoding : gob is a "private" encoding and decoding method provided by golang, so it will be difficult to call RPC services implemented in Go language from other languages
  • RPC framework of Go language
    • When packaging RPC data, you can implement custom encoding and decoding through plug-ins
    • RPC is built on the abstract io.ReadWriteCloser interface, we can build RPC on top of different communication protocols
  • Some of the better encodings supported by all languages
    • MessagePack: Efficient binary serialization format. It allows you to exchange data between multiple languages ​​such as JSON. but it's faster and smaller
    • JSON: text encoding
    • XML: text encoding
    • Protobuf binary encoding
  • json_tcp\service
package service

const (
	//统一rpc的注册名
	SERVICE_NAME = "HelloService"
)

type HelloService interface {
    
    
	Hello(request string, response *string) error
}

  • json_tcp\server\main.go
package main

import (
	"fmt"
	"log"
	"main/interface/service"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
)

// 通过接口约束HelloService服务
var _ service.HelloService = (*HelloService)(nil)

type HelloService struct {
    
    
}

func (p *HelloService) Hello(request string, response *string) error {
    
    
	*response = fmt.Sprintf("hello,%s", request)
	return nil
}

func main() {
    
    
	rpc.RegisterName(service.SERVICE_NAME, &HelloService{
    
    })
	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
    
    
		log.Fatal("ListenTCP error:", err)
	}
	for {
    
    
		conn, err := listener.Accept()
		if err != nil {
    
    
			log.Fatal("Accept error:", err)
		}
		go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
	}
}

  • json_tcp\client\main.go
package main

import (
	"fmt"
	"main/interface/service"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
)

// 约束客户端
var _ service.HelloService = (*HelloServiceClient)(nil)

func NewHelloServiceClient(network string, address string) (*HelloServiceClient, error) {
    
    
	conn, err := net.Dial(network, address)
	if err != nil {
    
    
		return nil, err
	}
	client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

	return &HelloServiceClient{
    
    
		client: client,
	}, nil
}

type HelloServiceClient struct {
    
    
	client *rpc.Client
}

func (c *HelloServiceClient) Hello(request string, response *string) error {
    
    
	return c.client.Call(fmt.Sprintf("%s.Hello", service.SERVICE_NAME), request, response)
}

func main() {
    
    
	c, err := NewHelloServiceClient("tcp", ":1234")
	if err != nil {
    
    
		panic(err)
	}
	var response string
	if err := c.Hello("tom", &response); err != nil {
    
    
		panic(err)
	}

	fmt.Println(response)
}

3 - json on http (to be added)

Three, prtotobuf encoding

1 - Overview of prtotobuf

  • Protobuf overview
    • Protobuf is the abbreviation of Protocol Buffers. It is a data description language developed by Google and open sourced in 2008.
    • When Protobuf was first open source, its positioning was similar to data description languages ​​such as XML and JSON. It generated code through accompanying tools and realized the function of serializing structured data.
    • Protobuf, as a description language for interface specifications, can be used as a basic tool for designing secure cross-language PRC interfaces
  • Reference options for codec tools
    • Codec efficiency
    • high compression ratio
    • multilingual support
      insert image description here
  • protobuf usage process
    insert image description here
  • Simple example of protobuf
    • syntax: Indicates that the syntax of proto3 is adopted. The third version of Protobuf refines and simplifies the language. All members are initialized with zero values ​​​​similar to those in the Go language (custom default values ​​are no longer supported), so message members no longer need to support the required feature.
    • package: indicates that the current package is the main package (this can be consistent with the package name of Go, simplifying the example code), of course, users can also customize the corresponding package path and name for different languages
    • option: some option parameters of protobuf, here specifies the path of the Go language package to be generated, other language parameters are different
    • The message: keyword defines a new String type, which corresponds to a String structure in the final generated Go language code. There is only one value member of string type in the String type, and the number 1 is used instead of the name when encoding this member
syntax = "proto3";

package hello;
option go_package="gitee.com/infraboard/go-course/day21/pb";

message String {
    
    
    string value = 1;
}

2 - protobuf compiler

  • Regarding data encoding :

    • In data description languages ​​such as XML or JSON, the corresponding data is generally bound by the name of the member
    • However, Protobuf encoding uses the unique number of the member to bind the corresponding data, so the size of the data after Protobuf encoding will be relatively small, but it is also very inconvenient for human inspection
    • We do not pay attention to the encoding technology of Protobuf at present. The final generated Go structure can freely adopt JSON or gob encoding formats, so you can temporarily ignore the member encoding part of Protobuf
  • protobuf compiler :

    • Function: Compile the definition file (.proto) (IDL: Interface Description Language) into data structures in different languages
    • Download address: https://github.com/protocolbuffers/protobuf/releases
    • Configure protoc.exe in bin to run in environment variables ->protoc --version
      insert image description here
  • Common command to view the set environment variable path :where protoc
    insert image description here

  • include compiler library : This overwrites the corresponding include file directory as needed
    insert image description here

  • Install the go language plugin for protobuf :

    • The core tool set of Protobuf is developed in C++ language, and Go language is not supported in the official protoc compiler. To generate the corresponding Go code based on the above hello.proto file, you need to install the corresponding plug-in
    • go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      insert image description here
  • Generate pb file :protoc -I="." --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/hello.proto

    • -I: -IPATH, --proto_path=PATH, specifies the path to search for proto files, if there are multiple paths, you can use -I to specify multiple times, if not specified, the default is the current directory
    • –go_out: --go refers to the name of the plugin. The plugin we installed is: protoc-gen-go, and protoc-gen is the plugin naming convention, and go is the plugin name, so here is –go, and –go_out means go The out parameter of the plug-in, here refers to the storage directory of the compiled product
    • –go_opt: The opt parameter of the protoc-gen-go plugin, where the module specifies the go module, and the generated go pkg will remove the module path and generate the corresponding pkg
    • pb/hello.proto: our proto file path
  • If you need to generate pb files for all current protos : go to the protobuf/pb directory and executeprotoc -I="." --go_out=. --go_opt=module="gitee.com/infraboard/go-course/day21/pb" *.proto
    insert image description here

  • Install dependent libraries : After generation, we can see that some libraries need to be used, which can be used go mod tidyfor synchronous installation
    insert image description here

3 - Serialization and deserialization

  • How to implement serialization and deserialization : Use the API provided by the google.golang.org/protobuf/proto tool for serialization and deserialization
    • Marshal: Serialization
    • UnMarshal: deserialization
  • Test serialization and deserialization : protobuf\pb\hello_test.go
package pb_test

import (
	"fmt"
	"main/protobuf/pb"
	"testing"

	"github.com/stretchr/testify/assert"
	"google.golang.org/protobuf/proto"
)

func TestMarshal(t *testing.T) {
    
    
	should := assert.New(t)
	str := &pb.String{
    
    Value: "hello"}

	// object -> protobuf -> []byte
	pbBytes, err := proto.Marshal(str)
	if should.NoError(err) {
    
    
		fmt.Println(pbBytes)
	}
	// []byte -> protobuf -> object
	obj := pb.String{
    
    }
	err = proto.Unmarshal(pbBytes, &obj)
	if should.NoError(err) {
    
    
		fmt.Println(obj.Value)
	}
}

insert image description here

4 - RPC based on protobuf (to be added 00:41:00 to 01:10:00)

  • Define the interactive data structure : pbrpc\service\pb\hello.proto
syntax = "proto3";

package hello;
option go_package="pbrpc/service";

message Request {
    
    
    string value = 1;
}

message Response {
    
    
    string value = 1;
}
  • Enter the path pbrpc :protoc -I="./service/pb/" --go_out=./service --go_opt=module="pbrpc/service" hello.proto
    insert image description here
    insert image description here
  • pbrpc\service\interface.go
package service

const (
	SERVICE_NAME = "HelloService"
)

type HelloService interface {
    
    
	Hello(request *Request, response *Response) error
}

Four, proto3 syntax

1 - Message definition

  • Define the message type
syntax = "proto3";

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
    
    
  string query = 1;
  int32 page_number = 2; // Which page number do we want?
  int32 result_per_page = 3;
}
  • The first line is the version of protobuf, we mainly talk about the definition syntax of message
    • comment: inject /* */ or //
    • message_name: within the same pkg, must be unique
    • filed_rule: can be absent, commonly used are repeated, oneof
    • filed_type: data type, the data type defined by protobuf, the production code will be mapped to the data type of the corresponding language
    • filed_name: field name, must be unique within the same message
    • field_number: field number, field number when serialized into binary data, must be unique within the same message, 1 ~ 15 is represented by 1 Byte, 16 ~ 2047 is represented by 2 Bytes
<comment>

message  <message_name> {
    
    
  <filed_rule>  <filed_type> <filed_name> = <field_number> 
        			类型         名称             编号  
}
  • If you want to reserve a number for later use you can use the reserved keyword statement
message Foo {
    
    
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

2 - Basic type

  • Value(Filed) Types : protobuf defines many Value Types, and its mapping relationship with other languages ​​is as follows

insert image description here

3 - Enumerated type

  • Use enum to declare enumerated types
syntax = "proto3";

package hello;
option go_package="gitee.com/infraboard/go-course/day21/pb";

enum Color {
    
    
    RED = 0;
    GREEN = 1;
    BLACK = 2;
}
  • Corresponding to the generated pb file
    insert image description here
  • Enum declaration syntax
    • enum_name: enumeration name
    • element_name: globally unique in pkg, very important
    • element_name: must start from 0, 0 means the default value of the type, 32-bit integer
enum <enum_name> {
    
    
    <element_name> = <element_number>
}
  • Alias : If you do have two enumeration requirements with the same name: for example, both TaskStatus and PipelineStatus require Running, you can add one:option allow_alias = true;
enum EnumAllowingAlias {
    
    
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }

insert image description here

  • Enums also support reserved values
enum Foo {
    
    
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

4 - Array type

  • The array type uses repeated
syntax = "proto3";

package hello;
option go_package="protobuf/pb";

message Host{
    
    
    string name = 1;
    string ip = 2;
}

message SearchResponse {
    
    
    int64 total = 1;
    //定义一个HOST数组
    repeated Host hosts = 2;
}

insert image description here
insert image description here

5 - Map

  • The syntax of protobuf declaration map :map<key_type, value_type> map_field = N;
syntax = "proto3";

package hello;
option go_package="protobuf/pb";

message Host{
    
    
    string name = 1;
    string ip = 2;
    map <string,string> tags = 3;
}

message SearchResponse {
    
    
    int64 total = 1;
    //定义一个HOST数组
    repeated Host hosts = 2;
}

insert image description here

6 - Oneof

  • Oneof : The limited type can only be one of them
message ProtobufEventHeader{
    
    
    string id = 1;
    map<string,string> headers = 2;
}

message JSONEventHeader{
    
    
    string id = 1;
    bytes headers = 2;
}

message Event{
    
    
    oneof header{
    
    
        ProtobufEventHeader protobuf = 1;
        JSONEventHeader json = 2;
    }
}
  • two ways to use
    • Use assertions to determine the type of one of:e.GetHeader()
    • Obtain directly through get to determine whether the return is nil
      • err1 := e.GetProtobuf()
      • err2 := e.GetJson()

7 - Any

  • Any : When we cannot clearly define the data type, we can use Any to represent
    • Note that the package needs to be imported:import "google/protobuf/any.proto"
    • If protoc reports an error, you need to specify the path of google protobuf in -I
// 这里是应用其他的proto文件, 后面会讲 ipmort用法
import "google/protobuf/any.proto";

message ErrorStatus {
    
    
  string message = 1;
  repeated google.protobuf.Any details = 2;
}
func TestAny(t *testing.T) {
    
    
	es := &pb.ErrorStatus{
    
    Message: "hello"}
	anyEs, err := anypb.New(es)

	if err != nil {
    
    
		panic("err")
	}
	fmt.Println(anyEs) //[type.googleapis.com/hello.ErrorStatus]:{message:"hello"}

	obj := pb.ErrorStatus{
    
    }
	anyEs.UnmarshalTo(&obj)
	fmt.Println(obj.Message) //hello
}

8 - Type nesting

  • Message can be nested in message (not recommended) : but anonymous nesting is not allowed, field name must be specified
message Outer {
    
                      // Level 0
    message MiddleAA {
    
      // Level 1
      message Inner {
    
       // Level 2
        int64 ival = 1;
        bool  booly = 2;
      }
    }
    message MiddleBB {
    
      // Level 1
      message Inner {
    
       // Level 2
        int32 ival = 1;
        bool  booly = 2;
      }
    }
  }

insert image description here
insert image description here

9 - Reference package

  • import “google/protobuf/any.proto”;
    • The above is the standard library to be read in this case. When we installed protoc, we have moved the changed lib to usr/local/include, so we can find
    • If our proto file is not in the /usr/local/include directory, we can add the search path through -I, so that the compiler can find the package we imported
      • protoc -I=. --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/import.proto

5. gRPC

1 - gRPC concept

  • What is grpc : gRPC is a cross-language open source RPC framework developed by Google based on Protobuf. gRPC is designed based on the HTTP/2 protocol and can provide multiple services based on one HTTP/2 link, which is more friendly to mobile devices

  • GRPC technology stack : Stub: The application communicates with the gRPC core library through the Stub code produced by the gRPC plug-in, or directly communicates with the gRPC core library

    • Data interaction format: protobuf
    • Communication method: The bottom layer is TCP or Unix Socket protocol, above which is the implementation of HTTP/2 protocol
    • Core library: On top of the HTTP/2 protocol, a gRPC core library for the Go language is built
    • Stub: The application communicates with the gRPC core library through the Stub code produced by the gRPC plug-in, or directly communicates with the gRPC core library
      insert image description here
  • Install the grpc plugin :

# protoc-gen-go 插件之前已经安装
# go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

# 安装protoc-gen-go-grpc插件
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
  • View the current version of the plugin : ``
    insert image description here

2 - Simple implementation of gRPC

  • grpc\server\pb\hello.proto
    • Enter the corresponding path according to the requirement: C:\develop_project\go_project\proj1\grpc\server
    • Build command:protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.proto
    • You can see that grpc\server\pb\hello_grpc.pb.go is generated
    • If it is the first generation, it needs to be synchronized:go mod tidy
syntax = "proto3";

package hello;
option go_package="grpc/server/pb";

//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" pb/hello.proto 

//grpc生成
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.proto

service HelloService{
    
    
    rpc hello (HelloRequest) returns(HelloResponse);
}

message HelloRequest{
    
    
    string value = 1;
}

message HelloResponse{
    
    
    string value = 1;
}

insert image description here

  • server:grpc\server\main.go
package main

import (
	"context"
	"fmt"
	"log"
	"main/grpc/server/pb"
	"net"

	"google.golang.org/grpc"
)

type HelloServiceServer struct {
    
    
	pb.UnimplementedHelloServiceServer
}

func (p *HelloServiceServer) Hello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
    
    
	return &pb.HelloResponse{
    
    
		Value: fmt.Sprintf("hello,%s", req.Value),
	}, nil
}

func main() {
    
    
	//把实现类注册给 GRPC Server
	server := grpc.NewServer()
	pb.RegisterHelloServiceServer(server, &HelloServiceServer{
    
    })

	listen, err := net.Listen("tcp", ":1234")
	if err != nil {
    
    
		panic(err)
	}

	log.Printf("grpc listen addr: 127.0.0.1:1234")

	//监听Socket,HTTP2内置
	if err := server.Serve(listen); err != nil {
    
    
		panic(err)
	}
}

  • client:grpc\client\main.go
package main

import (
	"context"
	"fmt"
	"main/grpc/server/pb"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
    
    
	//第一步,建立网络连接
	conn, err := grpc.DialContext(context.Background(), "127.0.0.1:1234", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
    
    
		panic(err)
	}

	//grpc为我们生成一个客户端调用服务端的SDK
	client := pb.NewHelloServiceClient(conn)
	resp, err := client.Hello(context.Background(), &pb.HelloRequest{
    
    Value: "tom"})
	if err != nil {
    
    
		panic(err)
	}
	fmt.Println(resp.Value)
}

insert image description here

3 - gRPC stream

  • Why use an RPC stream :
    • RPC is a remote function call, so the function parameters and return value of each call cannot be too large, otherwise it will seriously affect the response time of each call
    • Therefore, the traditional RPC method call is not suitable for uploading and downloading large data volume scenarios
    • To this end, the gRPC framework provides streaming features for the server and client respectively
  • How to formulate a gRPC stream :
    • The keyword stream specifies to enable the stream feature, the parameter part is the stream receiving the client parameters, and the return value is the stream returned to the client
    • The syntax for defining a gRPC stream:rpc <function_name> (stream <type>) returns (stream <type>) {}
  • grpc\server\pb\hello.proto
syntax = "proto3";

package hello;
option go_package="grpc/server/pb";

//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" pb/hello.proto 

//grpc生成
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.proto

service HelloService{
    
    
    rpc Channel(stream HelloRequest) returns(stream HelloResponse){
    
    }
}

message HelloRequest{
    
    
    string value = 1;
}

message HelloResponse{
    
    
    string value = 1;
}
  • grpc\server\main.go
package main

import (
	"fmt"
	"io"
	"log"
	"main/grpc/server/pb"
	"net"

	"google.golang.org/grpc"
)

type HelloServiceServer struct {
    
    
	pb.UnimplementedHelloServiceServer
}

func (p *HelloServiceServer) Channel(stream pb.HelloService_ChannelServer) error {
    
    
	for {
    
    
		//接收请求
		req, err := stream.Recv()
		if err != nil {
    
    
			if err == io.EOF {
    
    
				//当前客户端退出
				log.Printf("client closed")
				return nil
			}
			return err
		}
		fmt.Printf("recv req value : %s\n", req.Value)
		resp := &pb.HelloResponse{
    
    Value: fmt.Sprintf("hello,%s", req.Value)}
		//响应请求
		err = stream.Send(resp)
		if err != nil {
    
    
			if err == io.EOF {
    
    
				log.Printf("client closed")
				return nil
			}
			return err //服务端发送异常, 函数退出, 服务端流关闭
		}
	}
}

func main() {
    
    
	//把实现类注册给 GRPC Server
	server := grpc.NewServer()
	pb.RegisterHelloServiceServer(server, &HelloServiceServer{
    
    })

	listen, err := net.Listen("tcp", ":1234")
	if err != nil {
    
    
		panic(err)
	}

	log.Printf("grpc listen addr: 127.0.0.1:1234")

	//监听Socket,HTTP2内置
	if err := server.Serve(listen); err != nil {
    
    
		panic(err)
	}
}

  • grpc\client\main.go
package main

import (
	"context"
	"fmt"
	"main/grpc/server/pb"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
    
    
	//第一步,建立网络连接
	conn, err := grpc.DialContext(context.Background(), "127.0.0.1:1234", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
    
    
		panic(err)
	}

	//grpc为我们生成一个客户端调用服务端的SDK
	client := pb.NewHelloServiceClient(conn)
	stream, err := client.Channel(context.Background())
	if err != nil {
    
    
		panic(err)
	}

	//启用一个Goroutine来发送请求
	go func() {
    
    
		for {
    
    
			err := stream.Send((&pb.HelloRequest{
    
    Value: "tom"}))
			if err != nil {
    
    
				panic(err)
			}
			time.Sleep(1 * time.Second)
		}
	}()
	for {
    
    
		//主循环,负责接收服务端响应
		resp, err := stream.Recv()
		if err != nil {
    
    
			panic(err)
		}
		fmt.Println(resp)
	}
}

Guess you like

Origin blog.csdn.net/qq23001186/article/details/130197269