go 进阶 RPC相关: 二. gRPC入门

2023-11-01

一. 什么是grpc

  1. 参考博客
  1. 带入 gRPC
  2. Go-gRPC 实践指南
  3. gRPC 官方文档中文版
  4. grpc官网
  5. grpc-go源码库
  6. grpc-gateway源码库
  1. grpc是google开源的一个高性能RPC框架,基于HTTP2标准设计,支持普通的RPC也支持双向流式通信,相对于thrift,grpc可以多路复用,可以传递header头
  2. grpc链接的建立:
  1. 由于是基于http2实现的,http2又要求必须是https协议,所以与https大致相同,建立连接时除了三次握手外还存在一次tls握手
  2. 连接建立后会发起一个连接前言请求(Magic帧 Setting帧),
  3. 通过Win_up滑动窗口传输数据
  4. 基于Header/Data帧进行传播和设置操作
  5. grpc会将数据存放到Data中, Data通常采用protobuf,需要设置IDL结构体,编解码数据进行传输
  6. 请求执行完毕后通过四次挥手断开连接
    在这里插入图片描述
  1. grpc请求数据包分析
HEADERS (flags = END_HEADERS)
:method : POST // 请求的方法
:scheme : http // http/https
:path : /Comment/AddComment // 微服务路径 /{服务名}/{服务方法}
:authority : baidu.com // 目标 URL 的虚拟主机名
te : trailers  // gRPC 中 这个值必须为 trailers
grpc-timeout : 15 // 调用的超时时间
Content-Type : application/grpc // grpc 必须以 applaction/grpc 开头
grpc-encoding : gzip // 压缩类型
authorization : ***** 
  1. grpc响应数据包分析
HEADERS (flags = END_HEADERS)
:status : 200
grpc-encoding : gzip
Content-Type: application/grpc
  1. 注意: 消息体以二进制形式传输数据,开头是数据的长度,会以一个或多个帧来发送消息
  2. 请求消息结束会在最后一个数据帧上添加 END_STREAM 或发送一个带有 END_STREAM 空的数据帧
END_STREAM 也 称作 EOS(end of stream)
  1. 消息结束通过发送 trailer 来提醒客户端响应消息已发送
HEADERS (flags = END_STREAM, END_HEADERS)
grpc-status : 0 // OK / grcp 状态码
grpc-message : **** // 对错误的描述

二. grpc 相关插件安装

  1. 参考博客
  2. 开启go mod
go mod export GO111MODULE=on
  1. 开启代理
go mod export GOPROXY=https://goproxy.io
  1. 安装grpc
go get google.golang.org/grpc
  1. 安装proto,因为这些文件在安装grpc的时候,已经下载下来了,因此使用install命令就可以了,而不需要使用get命令
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
  1. 在执行install安装protoc-gen-go-grpc时,可能会报异常"没有必要的模块提供包",并且提示了添加获取方法,执行获取"protoc-gen-go-grp"命令
    在这里插入图片描述
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
  1. 上方命令执行完毕后会在"/GOPATH/bin/"目录下生成protoc-gen-go.exe与protoc-gen-go-grpc.exe两个可执行文件,查看这两个文件所在目录是否配置到了环境变量path中,如果没有添加到path中
    在这里插入图片描述
  2. 其它手动拉取protoc-gen-go命令
go get -u github.com/golang/prorobuf/protoc-gen-go
  1. cmd命令行窗口执行"protoc"校验是否安装成功
    在这里插入图片描述
  2. 问题:
    在这里插入图片描述

三. 编写Proto模板,生成代码

  1. 创建".proto"结尾的文件,编写用来生成go文件的模板,例如创建"my_test.proto"文件
//todo 指定生成proto时用的语法版本
syntax = "proto3";
//当前文件所在包
package test;
//todo 指定生成的go文件所在目录包,"."表示在当前目录生成,"proto"表示生成的go文件的包名是proto
option go_package = ".;proto";
//todo 定义请求参数类型
message TestRequest {
  //参数类型 变量名=标号
  string message = 1;
}
//todo 定义响应参数类型
message TestResponse {
  string message = 1;
  int32 code = 2;
  Corpus corpus = 4;
}
//todo 定义枚举
enum Corpus {
  UNIVERSAL = 0;
  WEB = 1;
  IMAGES = 2;
  LOCAL = 3;
  NEWS = 4;
  PRODUCTS = 5;
  VIDEO = 6;
}
//todo 定义server
service MyGrpc {
  //todo 定义接口
  //通信协议 方法名(入参类型) returns (反参类型){}
  rpc MethodOne(TestRequest) returns (TestResponse) {}
  //定义以流式响应的接口
  rpc ServerStreaming(TestRequest) returns (stream TestResponse) {}
  //定义以流式接收数据的接口
  rpc ClientStreaming(stream TestRequest) returns (TestResponse) {}
  //定义双向数据流接口
  rpc BidirectionalStreaming(stream TestRequest) returns (stream TestResponse) {}
}
  1. 切换到该文件所在目录,执行编译命令
protoc --go_out=. 文件名.proto
protoc --go-grpc_out=. 文件名.proto
  1. 编译命令执行完成后会生"文件名.pb.go"与"文件名_grpc.pb.go",在这两个文件中定义了方法的go语言实现,定义了请求与相应的go语言,就是通过protoc-gen-go把定义的语言无关的.proto转换为go语言的代码,以便server和client直接使用
  2. 问题: 网上很多教程中使用一下命令编译生成文件,为什么此处没有,因为下方的编译命令使用的是github版本的protoc-gen-go,而目前这个项目已经由Google接管了,并且该命令只会生成xxx.pb.go一个文件
protoc --go_out=plugins=grpc:. 文件名.proto
  1. protobuf 语法Go-gRPC 实践指南:Protobuf语法

四. grpc 服务器与客户端使用示例

  1. 基于上方.proto文件进行编译,生成server和client可以使用的go文件,先看一下"my_test.proto"文件,该文件内部定义了一个MyGrpc服务, 服务中存在4个接口,并且定义了接口执行需要的入参反参(注意不要使用proto作为文件名,可能会和标准库冲突)
    在这里插入图片描述
  2. 执行编译命令
protoc --go-grpc_out=. my_test.proto
protoc --go_out=. my_test.proto
  1. 执行编译后会生成"文件名.pb.go"与"文件名_grpc.pb.go"两个文件,

解释"_grpc.pb.go"结尾文件

  1. 查看以"_grpc.pb.go"结尾的文件,此处时my_test_grpc.pb.go
  2. “.proto"中定义了服务与接口,在”_grpc.pb.go"文件中生成了对应服务端的服务接口定义
    在这里插入图片描述

发现多生成了一个mustEmbedUnimplementedXxxx()接口,如果不需要该接口可以使用
“protoc --go-grpc_out=require_unimplemented_servers=false” 关闭,或者在grpc server实现结构体中匿名嵌入Unimplemented***Server结构体

  1. "_grpc.pb.go"文件中同时也生成了注册服务的方法
    在这里插入图片描述
  2. "_grpc.pb.go"文件中同时也生成了针对服务端接口定义的实现base实现?(如果实际开发中有接口不需实现,在创建服务结构体时,结构体中内嵌base结构体即可?)
    在这里插入图片描述
  3. 针对服务端的其它,例如流式处理的相关代码
func _MyGrpc_MethodOne_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(TestRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(MyGrpcServer).MethodOne(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/test.MyGrpc/MethodOne",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(MyGrpcServer).MethodOne(ctx, req.(*TestRequest))
	}
	return interceptor(ctx, in, info, handler)
}

func _MyGrpc_ServerStreaming_Handler(srv interface{}, stream grpc.ServerStream) error {
	m := new(TestRequest)
	if err := stream.RecvMsg(m); err != nil {
		return err
	}
	return srv.(MyGrpcServer).ServerStreaming(m, &myGrpcServerStreamingServer{stream})
}

type MyGrpc_ServerStreamingServer interface {
	Send(*TestResponse) error
	grpc.ServerStream
}

type myGrpcServerStreamingServer struct {
	grpc.ServerStream
}

func (x *myGrpcServerStreamingServer) Send(m *TestResponse) error {
	return x.ServerStream.SendMsg(m)
}

func _MyGrpc_ClientStreaming_Handler(srv interface{}, stream grpc.ServerStream) error {
	return srv.(MyGrpcServer).ClientStreaming(&myGrpcClientStreamingServer{stream})
}

type MyGrpc_ClientStreamingServer interface {
	SendAndClose(*TestResponse) error
	Recv() (*TestRequest, error)
	grpc.ServerStream
}

type myGrpcClientStreamingServer struct {
	grpc.ServerStream
}

func (x *myGrpcClientStreamingServer) SendAndClose(m *TestResponse) error {
	return x.ServerStream.SendMsg(m)
}

func (x *myGrpcClientStreamingServer) Recv() (*TestRequest, error) {
	m := new(TestRequest)
	if err := x.ServerStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

func _MyGrpc_BidirectionalStreaming_Handler(srv interface{}, stream grpc.ServerStream) error {
	return srv.(MyGrpcServer).BidirectionalStreaming(&myGrpcBidirectionalStreamingServer{stream})
}

type MyGrpc_BidirectionalStreamingServer interface {
	Send(*TestResponse) error
	Recv() (*TestRequest, error)
	grpc.ServerStream
}

type myGrpcBidirectionalStreamingServer struct {
	grpc.ServerStream
}

func (x *myGrpcBidirectionalStreamingServer) Send(m *TestResponse) error {
	return x.ServerStream.SendMsg(m)
}

func (x *myGrpcBidirectionalStreamingServer) Recv() (*TestRequest, error) {
	m := new(TestRequest)
	if err := x.ServerStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

// MyGrpc_ServiceDesc is the grpc.ServiceDesc for MyGrpc service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MyGrpc_ServiceDesc = grpc.ServiceDesc{
	ServiceName: "test.MyGrpc",
	HandlerType: (*MyGrpcServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "MethodOne",
			Handler:    _MyGrpc_MethodOne_Handler,
		},
	},
	Streams: []grpc.StreamDesc{
		{
			StreamName:    "ServerStreaming",
			Handler:       _MyGrpc_ServerStreaming_Handler,
			ServerStreams: true,
		},
		{
			StreamName:    "ClientStreaming",
			Handler:       _MyGrpc_ClientStreaming_Handler,
			ClientStreams: true,
		},
		{
			StreamName:    "BidirectionalStreaming",
			Handler:       _MyGrpc_BidirectionalStreaming_Handler,
			ServerStreams: true,
			ClientStreams: true,
		},
	},
	Metadata: "my_test.proto",
}
  1. "_grpc.pb.go"文件中同时也生成了针对客户端的服务与接口定义
    在这里插入图片描述
  2. "_grpc.pb.go"文件中同时也生成了针对客户端接口操作的实现?(可以直接拿来使用)
//客户端结构体
type myGrpcClient struct {
	cc grpc.ClientConnInterface
}
//创建客户端结构体方法
func NewMyGrpcClient(cc grpc.ClientConnInterface) MyGrpcClient {
	return &myGrpcClient{cc}
}

func (c *myGrpcClient) MethodOne(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) {
	out := new(TestResponse)
	err := c.cc.Invoke(ctx, "/test.MyGrpc/MethodOne", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (c *myGrpcClient) ServerStreaming(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (MyGrpc_ServerStreamingClient, error) {
	stream, err := c.cc.NewStream(ctx, &MyGrpc_ServiceDesc.Streams[0], "/test.MyGrpc/ServerStreaming", opts...)
	if err != nil {
		return nil, err
	}
	x := &myGrpcServerStreamingClient{stream}
	if err := x.ClientStream.SendMsg(in); err != nil {
		return nil, err
	}
	if err := x.ClientStream.CloseSend(); err != nil {
		return nil, err
	}
	return x, nil
}
  1. 以及客户端流式处理的相关代码
type MyGrpc_ServerStreamingClient interface {
	Recv() (*TestResponse, error)
	grpc.ClientStream
}

type myGrpcServerStreamingClient struct {
	grpc.ClientStream
}

func (x *myGrpcServerStreamingClient) Recv() (*TestResponse, error) {
	m := new(TestResponse)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

func (c *myGrpcClient) ClientStreaming(ctx context.Context, opts ...grpc.CallOption) (MyGrpc_ClientStreamingClient, error) {
	stream, err := c.cc.NewStream(ctx, &MyGrpc_ServiceDesc.Streams[1], "/test.MyGrpc/ClientStreaming", opts...)
	if err != nil {
		return nil, err
	}
	x := &myGrpcClientStreamingClient{stream}
	return x, nil
}

type MyGrpc_ClientStreamingClient interface {
	Send(*TestRequest) error
	CloseAndRecv() (*TestResponse, error)
	grpc.ClientStream
}

type myGrpcClientStreamingClient struct {
	grpc.ClientStream
}

func (x *myGrpcClientStreamingClient) Send(m *TestRequest) error {
	return x.ClientStream.SendMsg(m)
}

func (x *myGrpcClientStreamingClient) CloseAndRecv() (*TestResponse, error) {
	if err := x.ClientStream.CloseSend(); err != nil {
		return nil, err
	}
	m := new(TestResponse)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

func (c *myGrpcClient) BidirectionalStreaming(ctx context.Context, opts ...grpc.CallOption) (MyGrpc_BidirectionalStreamingClient, error) {
	stream, err := c.cc.NewStream(ctx, &MyGrpc_ServiceDesc.Streams[2], "/test.MyGrpc/BidirectionalStreaming", opts...)
	if err != nil {
		return nil, err
	}
	x := &myGrpcBidirectionalStreamingClient{stream}
	return x, nil
}

type MyGrpc_BidirectionalStreamingClient interface {
	Send(*TestRequest) error
	Recv() (*TestResponse, error)
	grpc.ClientStream
}

type myGrpcBidirectionalStreamingClient struct {
	grpc.ClientStream
}

func (x *myGrpcBidirectionalStreamingClient) Send(m *TestRequest) error {
	return x.ClientStream.SendMsg(m)
}

func (x *myGrpcBidirectionalStreamingClient) Recv() (*TestResponse, error) {
	m := new(TestResponse)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

解释".pb.go"结尾文件

  1. “.proto"中定义了枚举, 在”.pb.go"中生成了对应的枚举以及相关方法
    在这里插入图片描述
  2. “.proto"中定义了结构体,在”.pb.go"中生成了对应的结构体以及相关方法
    在这里插入图片描述

服务端代码示例

  1. 创建结构体,实现"_grpc.pb.go"中对应服务端的所有接口,创建grpc服务器,调用"_grpc.pb.go"中生成的注册服务接口,将服务实现注册到grpc服务器,监听指定端口,启动服务即可
import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"io"
	"log"
	"net"
)

// 1.创建服务结构体
type Server struct {
}

// 2.实现所有接口
// 2.1实现一元接口(普通接口)示例
func (s *Server) MethodOne(ctx context.Context, in *TestRequest) (*TestResponse, error) {
	fmt.Printf("--- UnaryEcho ---\n")
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		log.Println("miss metadata from context")
	}
	fmt.Println("md", md)
	fmt.Printf("request received: %v, sending echo\n", in)
	return &TestResponse{Message: in.Message}, nil
}

// 2.2流式响应接口实现
func (s *Server) ServerStreaming(in *TestRequest, stream MyGrpc_ServerStreamingServer) error {
	fmt.Printf("--- ServerStreamingEcho ---\n")
	fmt.Printf("request received: %v\n", in)
	// Read requests and send responses.
	for i := 0; i < 10; i++ {
		fmt.Printf("echo message %v\n", in.Message)
		//stream.Send()发送数据
		err := stream.Send(&TestResponse{Message: in.Message})
		if err != nil {
			return err
		}
	}
	return nil
}

// 2.3流式接收接口实现
func (s *Server) ClientStreaming(stream MyGrpc_ClientStreamingServer) error {
	fmt.Printf("--- ClientStreamingEcho ---\n")
	// Read requests and send responses.
	var message string
	for {
		//stream.Recv()接收数据
		in, err := stream.Recv()
		if err == io.EOF {
			fmt.Printf("echo last received message\n")
			return stream.SendAndClose(&TestResponse{Message: message})
		}
		message = in.Message
		fmt.Printf("request received: %v, building echo\n", in)
		if err != nil {
			return err
		}
	}
}

// 2.4双向数据流接口实现
func (s *Server) BidirectionalStreaming(stream MyGrpc_BidirectionalStreamingServer) error {
	fmt.Printf("--- BidirectionalStreamingEcho ---\n")
	// Read requests and send responses.
	for {
		//stream.Recv()接收数据
		in, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		fmt.Printf("request received %v, sending echo\n", in)
		//stream.Send()发送数据
		if err := stream.Send(&TestResponse{Message: in.Message}); err != nil {
			return err
		}
	}
}

// 不太清除这个接口是做什么的,假设不想生成这个接口可以执行 "protoc --go-grpc_out=require_unimplemented_servers=false" 关闭,
// 或者实现grpcServer的结构体中匿名嵌入UnimplementedXXXServer这个base结构体
func (s *Server) mustEmbedUnimplementedMyGrpcServer() {}
  1. main方法运行示例
// 3.main方法启动服务
func main() {

	//1.监听tcp,指定端口
	lis, err := net.Listen("tcp", ":8080")
	if nil != err {
		log.Fatalf("listen: %v", err)
	}
	//2.创建grpc服务器
	s := grpc.NewServer()

	//3.将服务实现注册到grpc服务器
	RegisterMyGrpcServer(s, &Server{})

	//4.监听启动服务
	s.Serve(lis)
}

客户端代码示例

  1. 客户端根据业务需求,编写代码,调用生成的服务端接口,实现请求服务端指定接口业务
import (
	"context"
	"fmt"
	"io"
	"log"
	"sync"
	"time"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

// 请求服务端普通接口示例
func MethodOneCallWithMetadata(c MyGrpcClient, message string) {
	//1.设置请求头
	md := metadata.Pairs("timestamp", time.Now().Format(time.StampNano))
	md.Append("authorization", "Bearer AccessToken")
	//2.创建ctx
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	//3.设置请求参数,并调用生成的指定方法MethodOne()请求服务端
	r, err := c.MethodOne(ctx, &TestRequest{Message: message})
	if err != nil {
		log.Fatalf("failed to call UnaryEcho: %v", err)
	}
	fmt.Printf("response:%v\n", r.Message)
}

// 请求服务端流式响应接口示例
func serverStreamingWithMetadata(c MyGrpcClient, message string) {
	//1.设置请求头
	md := metadata.Pairs("timestamp", time.Now().Format(time.StampNano))
	md.Append("authorization", "Bearer AccessToken")
	//2.创建ctx
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	//3.服务端流式响应,调用生成的流式响应方法,构建流式响应stream
	stream, err := c.ServerStreaming(ctx, &TestRequest{Message: message})
	if err != nil {
		log.Fatalf("failed to call ServerStreamingEcho: %v", err)
	}

	//4.遍历接收服务端响应
	// Read all the responses.
	var rpcStatus error
	for {
		//stream.Recv()接收流中的数据
		r, err := stream.Recv()
		if err != nil {
			rpcStatus = err
			break
		}
		fmt.Printf(" - %s\n", r.Message)
	}
	if rpcStatus != io.EOF {
		log.Fatalf("failed to finish server streaming: %v", rpcStatus)
	}
}

// 流式请求服务端接口示例
func clientStreamWithMetadata(c MyGrpcClient, message string) {
	//1.设置请求头
	md := metadata.Pairs("timestamp", time.Now().Format(time.StampNano))
	md.Append("authorization", "Bearer AccessToken")
	//2.创建ctx
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	//3.客户端流式发送数据到服务端,调用生成的流式方法构建流式数据stream
	stream, err := c.ClientStreaming(ctx)
	if err != nil {
		log.Fatalf("failed to call ClientStreamingEcho: %v\n", err)
	}

	//4.以流形式多次向服务端发送数据
	for i := 0; i < 10; i++ {
		//5.基于流式结构发送数据到服务端
		if err := stream.Send(&TestRequest{Message: message}); err != nil {
			log.Fatalf("failed to send streaming: %v\n", err)
		}
	}

	//6.接收服务端响应并关闭连接
	r, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("failed to CloseAndRecv: %v\n", err)
	}
	fmt.Printf("response:%v\n", r.Message)
}

// 双向数据流接口示例
func bidirectionalWithMetadata(c MyGrpcClient, message string) {
	//1.设置请求头
	md := metadata.Pairs("timestamp", time.Now().Format(time.StampNano))
	md.Append("authorization", "Bearer AccessToken")
	//2.创建ctx
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	//3.客户端服务端双写流式通信,调用生成的方法创建双向流stream
	stream, err := c.BidirectionalStreaming(ctx)
	if err != nil {
		log.Fatalf("failed to call BidirectionalStreamingEcho: %v\n", err)
	}

	//4.协程内部多次向服务端以流的形式发送数据
	go func() {
		// Send all requests to the server.
		for i := 0; i < 10; i++ {
			if err := stream.Send(&TestRequest{Message: message}); err != nil {
				log.Fatalf("failed to send streaming: %v\n", err)
			}
		}
		stream.CloseSend()
	}()

	//5..以流的形式接收服务端响应数据
	var rpcStatus error
	fmt.Printf("response:\n")
	for {
		r, err := stream.Recv()
		if err != nil {
			rpcStatus = err
			break
		}
		fmt.Printf(" - %s\n", r.Message)
	}
	if rpcStatus != io.EOF {
		log.Fatalf("failed to finish server streaming: %v", rpcStatus)
	}
}
  1. main方法,创建grpc客户端,绑定服务端地址,调用该服务端生成的接口(也就是上方针对服务端生成的接口封装的业务接口)发起请求
func main() {
	//遍历执行模拟并发
	wg := sync.WaitGroup{}
	for i := 0; i < 1; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			//1.与服务端建立连接
			conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
			if err != nil {
				log.Fatalf("did not connect: %v", err)
			}
			//退出时的关闭
			defer conn.Close()

			//2.调用生成的创建client方法,
			//基于生成的Client创建
			//grpcClient连接,并绑定连接
			c := NewMyGrpcClient(conn)

			//3.请求服务端一元方法(普通方法)
			//for i := 0; i < 100; i++ {
			MethodOneCallWithMetadata(c, message)
			time.Sleep(400 * time.Millisecond)
			//}
			//
			//4.请求服务端,流式响应方法
			serverStreamingWithMetadata(c, message)
			time.Sleep(1 * time.Second)

			//5.流式请求服务端接口
			clientStreamWithMetadata(c, message)
			time.Sleep(1 * time.Second)

			//6.双向流式
			bidirectionalWithMetadata(c, message)
		}()
	}
	wg.Wait()
	time.Sleep(1 * time.Second)
}

五. grpc 拦截器

  1. grpc服务端和客户端都提供了interceptor功能,类似middleware,可以在拦截器中定义指定业务,例如验证处理,日志等
  2. Go-gRPC 实践指南: Interceptor 拦截器

六 gRpc底层通信的一些问题总结

  1. gRPC底层通信协议: gRPC底层使用的HTTP/2协议实现,查看请求响应头中的Content-Type会设置为 application/grpc
  1. 基于https建立连接时除了三次握手外还存在一次tls握手
  2. 连接建立后会发起一个连接前言请求(Magic帧 Setting帧),
  3. 通过Win_up滑动窗口传输数据
  4. 基于Header/Data帧进行传播和设置操作
  5. grpc会将数据存放到Data中, Data通常采用protobuf,需要设置IDL结构体,编解码数据进行传输
  6. 请求执行完毕后通过四次挥手断开连接
  1. gRPC 支持 4 种基础通信模式: 一元 RPC、服务器端流 RPC、客户端流 RPC 以及双向流 RPC
  2. 一元RPC模式: 在一元 RPC 模式中,gRPC 服务器端和 gRPC 客户端在通信时始终只有一个请求和一个响应。
  3. 服务器端流 RPC模式:
  1. 服务器端在接收到客户端的请求消息后,会发回一个响应的序列。这种多个响应所组成的序列也被称为“流”。在将所有的服务器端响应发送完毕之后,服务器端会以 trailer 元数据的形式将其状态发送给客户端,从而标记流的结束
  2. 客户端的 Go 语言实现中使用 Recv 方法从客户端流中检索消息,并且持续检索,直到流结束为止
  1. 客户端流RPC模式
  1. 客户端会发送多个请求给服务器端,而不再是单个请求,注意服务器端不一定要等到从客户端接收到所有消息后才发送响应,可以在接收到流中的一条消息或几条消息之后就发送响应,也可以在读取完流中的所有消息之后再发送响应
  1. 其实现原理是: 在grpc通信时,消息体以二进制形式一个或多个帧来发送消息数据,请求消息结束时会在最后一个数据帧上添加 END_STREAM 或发送一个带有 END_STREAM 空的数据帧也就是trailer,接收方会持续监测trailer,接收到后才认为结束
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

go 进阶 RPC相关: 二. gRPC入门 的相关文章

  • 为什么 Hashtable 不允许空键或空值?

    正如 JDK 文档中所指定的 Hashtable 不允许空键或空值 HashMap 允许一个空键和任意数量的空值 为什么是这样 Hashtable 是较旧的类 通常不鼓励使用它 也许他们看到了对 null 键的需要 更重要的是 null 值
  • 从txt文件中读取数据而不下载它?

    我想从提供的文本文件中解析信息 有没有一种方法可以在应用程序中执行此操作 而无需先下载文件 以某种方式传输文本内容 打开到 URL 的 Http 连接 使用内置 HttpURLConnection 或使用 commons httpclien
  • 在Java Servlet中获取通过jquery ajax发送的参数[重复]

    这个问题在这里已经有答案了 我在网上搜索这个主题 但找不到有效的示例 我会很高兴有人能给我帮助 这就是我测试的 ajax url GetJson type POST dataType json contentType application
  • openFileOutput 在单例类中无法正常工作 - 想法/解决方法?

    作为一名 Android 开发新手 我遇到了一些奇怪的问题 我想创建一个类 它方法其他类 活动 任何可以用于以某种特殊方式处理文件的类 假设为了简单起见 我们将记录一些内容 如果我在活动中执行以下操作 例如在 OnClick 侦听器中 则一
  • ZeroDateTimeBehavior=convertToNull 在使用 hibernate 的 jdbc url 中不起作用

    通过 extern 属性文件 url 指定如下 jdbc mariadb xxxxx 3306 xxxxx zeroDateTimeBehavior convertToNull 连接工作正常并且能够查询数据库 通过休眠 我创建了一个映射到带
  • java中高效的输入流到字符串方法

    因此 我在 Java 中的 诚然非常简单 应用程序上运行探查器 令我惊讶的是 仅次于需要在时间上发出 HTTP 请求的方法的是我的方法 inputStreamToString方法 目前它的定义如下 public static String
  • Android Studio 与 Google Play 服务的编译问题

    我正在运行 Android Studio 0 8 4 并在 Android Studio 0 8 2 上尝试过此操作 我正在运行 Java JDK 1 8 0 11 并尝试使用 JDK 1 8 0 05 每当我尝试构建我的 android
  • 无法在 Intellij 中运行主类[重复]

    这个问题在这里已经有答案了 我有以下项目结构 ProjectRoot src Main examples libs My src文件夹被标记为sources在 Intellij 中 现在 当我想运行 Main 类时 出现以下错误 Excep
  • 如何杀死 Java Future?

    我正在开发的服务使用 Future 来并行运行多个任务 每个任务最多可能需要一分钟才能完成 然而 外部库似乎有问题 因为在某些情况下 2 的时间 它不会返回 在这些情况下 我想给出 2 分钟的等待时间 如果还没有返回 我想杀死 future
  • 清空变量不会使方法引用无效[重复]

    这个问题在这里已经有答案了 为什么代码不抛出NullPointerException当我使用与变量绑定的方法引用时dog我后来分配了null to 我正在使用 Java 8 import java util function Functio
  • 使用 https 的 Web 服务身份验证给出错误

    我编写了一个简单的 Web 服务 并使用摘要和 HTTPS 身份验证来保护它 我已经使用 Java 中的 keytool 生成了我的证书 当我通过创建 war 文件在 Tomcat 中部署 Web 服务时 axis 的欢迎页面正确显示 但是
  • 是否可以使用 Apache Tika 提取表信息?

    我正在寻找 pdf 和 MS Office 文档格式的解析器 以从文件中提取表格信息 当我看到 Apache Tika 时 正在考虑编写单独的实现 我能够从任何这些文件格式中提取全文 但我的要求是提取表格数据 我希望有 2 列采用键值格式
  • 如何使用 aether 从 Java 找到最新版本的 Maven 工件?

    他们的文档非常薄弱 我无法弄清楚 我找到了部分答案here https stackoverflow com questions 27428068 how to retrieve the latest also snapshot versio
  • java绕中心旋转矩形

    我想围绕其中心点旋转一个矩形 它应该保留在应该绘制的位置并在该空间中旋转 这是我的代码 AffineTransform transform new AffineTransform transform rotate Math toRadian
  • 始终将双精度舍入

    我怎么总是能把一个double to an int 并且永远不要将其四舍五入 我知道Math round double 但我希望它始终向上舍入 所以如果是的话3 2 四舍五入为 4 您可以使用Math ceil method 请参阅Java
  • Spring Security 角色层次结构不适用于 Thymeleaf sec:authorize

    我正在使用 Spring Security 3 2 5 RELEASE 和 ThymeLeaf 2 1 4 RELEASE 我已经在安全上下文中定义了角色层次结构 在我的视图层中我正在使用sec authorize属性来定义菜单项 我希望看
  • Java 8:如何创建毫秒、微秒或纳秒的 DateTimeFormatter?

    我需要创建格式化程序来解析具有可选的毫秒 微米或纳秒分数的时间戳 例如 对于我的需求 我看到以下机会 DateTimeFormatter formatter new DateTimeFormatterBuilder append DateT
  • Hybris:如何在impex中导入zip文件中的媒体?

    我知道我们可以导入未像这样压缩的图像 siteResource jar com project initialdata constants ProjectInitialDataConstants projectinitialdata imp
  • 在 Kotlin 中声明静态属性?

    My Java code public class Common public static ModelPengguna currentModelPengguna public class Common companion object v
  • MyBatis 枚举的使用

    我知道以前有人问过这个问题 但我无法根据迄今为止找到的信息实施解决方案 所以也许有人可以向我解释一下 我有一个表 状态 它有两列 id 和 name id是PK 我不想使用 POJO Status 而是使用枚举 我创建了这样一个枚举 如下所

随机推荐