14、RPC与gRPC

news2024/11/14 13:23:37

目录

  • 一、rpc基础
    • 1 - rpc入门
    • 2 - 基础的rpc通信
    • 3 - 基于接口的RPC服务
  • 二、rpc编码
    • 1 - gob编码
    • 2 - json on tcp
    • 3 - json on http(待补充)
  • 三、prtotobuf编码
    • 1 - prtotobuf概述
    • 2 - protobuf编译器
    • 3 - 序列化和反序列化
    • 4 - 基于protobuf的RPC(待补充00:41:00到01:10:00)
  • 四、proto3语法
    • 1 - 消息定义
    • 2 - 基础类型
    • 3 - 枚举类型
    • 4 - 数组类型
    • 5 - Map
    • 6 - Oneof
    • 7 - Any
    • 8 - 类型嵌套
    • 9 - 引用包
  • 五、gRPC
    • 1 - gRPC概念
    • 2 - gRPC简单实现
    • 3 - gRPC流

一、rpc基础

1 - rpc入门

  • ipc:Inter-Process Communication,进程间通信,进程间通信是指两个进程的数据之间产生交互
  • rpc:RPC是远程过程调用的简称,是分布式系统中不同节点间流行的通信方式
    • RPC传输协议
    • 消息序列化与反序列化
      在这里插入图片描述

2 - 基础的rpc通信

  • 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)
}

在这里插入图片描述

3 - 基于接口的RPC服务

  • rpc方法存在的优化
    • client call 方法, 里面3个参数2个interface{}, 你再使用的时候 可能真不知道要传入什么, 这就好像你写了一个HTTP的服务, 没有接口文档, 容易调用错误
    • 或者说,如何对rpc接口进行一次封装,用来限定接口的参数类型
// 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:添加接口约束
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:添加接口约束
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)
}

二、rpc编码

1 - gob编码

  • 什么是gob编码:标准库的RPC默认采用Go语言特有的gob编码,标准库gob是golang提供的“私有”的编解码方式,它的效率会比json,xml等更高,特别适合在Go语言程序间传递数据
  • 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

  • gob编码的缺点:gob是golang提供的“私有”的编解码方式,因此从其它语言调用Go语言实现的RPC服务将比较困难
  • Go语言的RPC框架
    • RPC数据打包时可以通过插件实现自定义的编码和解码
    • RPC建立在抽象的io.ReadWriteCloser接口之上的,我们可以将RPC架设在不同的通讯协议之上
  • 所有语言都支持的比较好的一些编码
    • MessagePack: 高效的二进制序列化格式。它允许你在多种语言(如JSON)之间交换数据。但它更快更小
    • JSON: 文本编码
    • XML:文本编码
    • Protobuf 二进制编码
  • 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(待补充)

三、prtotobuf编码

1 - prtotobuf概述

  • Protobuf概述
    • Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,并于2008年对外开源
    • Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能
    • Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础工具
  • 编解码工具的参考选项
    • 编解码效率
    • 高压缩比
    • 多语言支持
      在这里插入图片描述
  • protobuf使用流程
    在这里插入图片描述
  • protobuf简单例子
    • syntax: 表示采用proto3的语法。第三版的Protobuf对语言进行了提炼简化,所有成员均采用类似Go语言中的零值初始化(不再支持自定义默认值),因此消息成员也不再需要支持required特性
    • package:指明当前是main包(这样可以和Go的包名保持一致,简化例子代码),当然用户也可以针对不同的语言定制对应的包路径和名称
    • option:protobuf的一些选项参数, 这里指定的是要生成的Go语言package路径, 其他语言参数各不相同
    • message: 关键字定义一个新的String类型,在最终生成的Go语言代码中对应一个String结构体。String类型中只有一个字符串类型的value成员,该成员编码时用1编号代替名字
syntax = "proto3";

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

message String {
    string value = 1;
}

2 - protobuf编译器

  • 关于数据编码

    • 在XML或JSON等数据描述语言中,一般通过成员的名字来绑定对应的数据
    • 但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据,因此Protobuf编码后数据的体积会比较小,但是也非常不便于人类查阅
    • 我们目前并不关注Protobuf的编码技术,最终生成的Go结构体可以自由采用JSON或gob等编码格式,因此大家可以暂时忽略Protobuf的成员编码部分
  • protobuf编译器

    • 作用:把定义文件(.proto)(IDL: 接口描述语言), 编译成不同语言的数据结构
    • 下载地址:https://github.com/protocolbuffers/protobuf/releases
    • 将bin中的protoc.exe配置到环境变量中运行 -> protoc --version
      在这里插入图片描述
  • 通用命令查看已设置的环境变量路径where protoc
    在这里插入图片描述

  • include编译器库:这个根据需要,覆盖对应的include文件目录
    在这里插入图片描述

  • 安装protobuf的go语言插件

    • Protobuf核心的工具集是C++语言开发的,在官方的protoc编译器中并不支持Go语言。要想基于上面的hello.proto文件生成相应的Go代码,需要安装相应的插件
    • go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      在这里插入图片描述
  • 生成pb文件protoc -I="." --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/hello.proto

    • -I:-IPATH, --proto_path=PATH, 指定proto文件搜索的路径, 如果有多个路径 可以多次使用-I 来指定, 如果不指定默认为当前目录
    • –go_out: --go指插件的名称, 我们安装的插件为: protoc-gen-go, 而protoc-gen是插件命名规范, go是插件名称, 因此这里是–go, 而–go_out 表示的是 go插件的 out参数, 这里指编译产物的存放目录
    • –go_opt: protoc-gen-go插件opt参数, 这里的module指定了go module, 生成的go pkg 会去除掉module路径,生成对应pkg
    • pb/hello.proto: 我们proto文件路径
  • 如果需要为当前所有的proto生成pb文件:进入到protobuf/pb目录执行protoc -I="." --go_out=. --go_opt=module="gitee.com/infraboard/go-course/day21/pb" *.proto
    在这里插入图片描述

  • 安装依赖库:生成之后我们可以看到有需要用到一些库,这里可以使用go mod tidy进行同步安装
    在这里插入图片描述

3 - 序列化和反序列化

  • 如何实现序列化和反序列化:使用google.golang.org/protobuf/proto工具提供的API来进行序列化与反序列化
    • Marshal:序列化
    • UnMarshal:反序列化
  • 测试序列化与反序列化: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)
	}
}

在这里插入图片描述

4 - 基于protobuf的RPC(待补充00:41:00到01:10:00)

  • 定义交互数据结构:pbrpc\service\pb\hello.proto
syntax = "proto3";

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

message Request {
    string value = 1;
}

message Response {
    string value = 1;
}
  • 进入路径pbrpcprotoc -I="./service/pb/" --go_out=./service --go_opt=module="pbrpc/service" hello.proto
    在这里插入图片描述
    在这里插入图片描述
  • pbrpc\service\interface.go
package service

const (
	SERVICE_NAME = "HelloService"
)

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

四、proto3语法

1 - 消息定义

  • 定义消息类型
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;
}
  • 第一行是protobuf的版本, 我们主要讲下 message 的定义语法
    • comment: 注射 /* */或者 //
    • message_name: 同一个pkg内,必须唯一
    • filed_rule: 可以没有, 常用的有repeated, oneof
    • filed_type: 数据类型, protobuf定义的数据类型, 生产代码的会映射成对应语言的数据类型
    • filed_name: 字段名称, 同一个message 内必须唯一
    • field_number: 字段的编号, 序列化成二进制数据时的字段编号, 同一个message 内必须唯一, 1 ~ 15 使用1个Byte表示, 16 ~ 2047 使用2个Byte表示
<comment>

message  <message_name> {
  <filed_rule>  <filed_type> <filed_name> = <field_number> 
        			类型         名称             编号  
}
  • 如果你想保留一个编号,以备后来使用可以使用 reserved 关键字声明
message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

2 - 基础类型

  • Value(Filed) Types:protobuf 定义了很多Value Types, 他和其他语言的映射关系如下

在这里插入图片描述

3 - 枚举类型

  • 使用enum来声明枚举类型
syntax = "proto3";

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

enum Color {
    RED = 0;
    GREEN = 1;
    BLACK = 2;
}
  • 对应生成的pb文件
    在这里插入图片描述
  • 枚举声明语法
    • enum_name: 枚举名称
    • element_name: pkg内全局唯一, 很重要
    • element_name: 必须从0开始, 0表示类型的默认值, 32-bit integer
enum <enum_name> {
    <element_name> = <element_number>
}
  • 别名:如果你的确有2个同名的枚举需求: 比如 TaskStatus 和 PipelineStatus 都需要Running,就可以添加一个: option allow_alias = true;
enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }

在这里插入图片描述

  • 枚举也支持预留值
enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

4 - 数组类型

  • 数组类型使用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;
}

在这里插入图片描述
在这里插入图片描述

5 - Map

  • protobuf 声明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;
}

在这里插入图片描述

6 - Oneof

  • Oneof:限定类型只能是其中一种
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;
    }
}
  • 两种使用方法
    • 采用断言来判断one of的类型:e.GetHeader()
    • 直接通过get获取,判断返回是否为nil
      • err1 := e.GetProtobuf()
      • err2 := e.GetJson()

7 - Any

  • Any:当我们无法明确定义数据类型的时候, 可以使用Any表示
    • 注意需要引入包:import "google/protobuf/any.proto"
    • protoc如果报错,需要在-I中指定google protobuf的路径
// 这里是应用其他的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 - 类型嵌套

  • 可以再message里面嵌套message(不建议使用): 但是不允许 匿名嵌套, 必须指定字段名称
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;
      }
    }
  }

在这里插入图片描述
在这里插入图片描述

9 - 引用包

  • import “google/protobuf/any.proto”;
    • 上面这在情况就是读取的标准库, 我们在安装protoc的时候, 已经把改lib 挪到usr/local/include下面了,所以可以找到
    • 如果我们proto文件并没有在/usr/local/include目录下, 通过-I 可以添加搜索的路径, 这样就编译器就可以找到我们引入的包了
      • protoc -I=. --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/import.proto

五、gRPC

1 - gRPC概念

  • 什么是grpc:gRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。gRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好

  • GRPC技术栈:Stub: 应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信

    • 数据交互格式: protobuf
    • 通信方式: 最底层为TCP或Unix Socket协议,在此之上是HTTP/2协议的实现
    • 核心库: 在HTTP/2协议之上又构建了针对Go语言的gRPC核心库
    • Stub: 应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信
      在这里插入图片描述
  • 安装grpc插件

# 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
  • 查看当前插件的版本:``
    在这里插入图片描述

2 - gRPC简单实现

  • grpc\server\pb\hello.proto
    • 根据需求进入对应的路径: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
    • 可以看到生成了grpc\server\pb\hello_grpc.pb.go
    • 如果是第一次生成还需要同步下: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;
}

在这里插入图片描述

  • 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)
}

在这里插入图片描述

3 - gRPC流

  • 为什么要使用个RPC流
    • RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间
    • 因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合
    • 为此,gRPC框架针对服务器端和客户端分别提供了流特性
  • 如何制定gRPC流
    • 关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流
    • 定义gRPC流的语法: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)
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448811.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TiDB实战篇-TiCDC

目录 简介 原理 使用场景 使用限制 硬件配置 部署 在安装TiDB的时候部署 扩容部署 操作 管理CDC 管理工具 查看状态 创建同步任务 公共参数 CDC任务同步到MySQL实战 同步命令 查看所有的同步任务 同步任务的状态 管理同步任务 查看一个同步信息的具体情况 …

腾讯云COS+ElmentUI+SpringBoot视频上传功能实现

文章目录 第一步&#xff1a;选择合适的组件并整合到项目中第二步&#xff1a;前端校验第三步&#xff1a;绑定上传成功方法第四步&#xff1a;腾讯云cos后端接口配置 今天在做项目的时候需要完成一个视频上传的功能&#xff0c;这里做一个记录&#xff01; 第一步&#xff1a;…

春秋云境:CVE-2022-25411(Maxsite CMS文件上传漏洞)

目录 一、题目 二、官方POC 一、题目 介绍&#xff1a; MaxSite CMS是俄国MaxSite CMS开源项目的一款网站内容管理系统 Maxsite CMS存在文件上传漏洞&#xff0c;攻击者可利用该漏洞通过精心制作的PHP文件执行任意代码。 进入题目&#xff1a; 发现是俄文 乌拉~~ 不过没…

MongoDB副本集

一、介绍 MongoDB副本集(Replica set)是一组MongoDB实例,它们都维护着相同的数据集合。MongoDB副本集是有自动故障恢复功能的主从集群,有一个Primary节点和一个或多个Secondary节点组成。副本集没有固定的主节点,当主节点发生故障时整个集群会选举一个主节点为系统提供服务…

6.S081——Lab3——page table

0.briefly speaking 这是6.S081 Fall 2021的第三个实验&#xff0c;是页表和虚拟内存相关的实验&#xff0c;在之前我们已经详细地阅读了Xv6内核中有关虚拟内存管理的源代码&#xff0c;现在就可以深入一下这个实验了。本实验分为如下三个小任务&#xff1a; Speed up system…

【Mac-m2芯片docker安装AWVS及问题解决】

【Mac-m2芯片docker安装AWVS及问题解决】 docker安装AWVS安装报错问题解决 docker安装 docker安装命令&#xff1a; brew install --cask --appdir/Applications docker 查看是否安装成功&#xff1a; docker --version docker info 换源&#xff1a; “https://hub-mirror.c.…

在Python中使用牛顿法

牛顿法简介 牛顿法&#xff08;Newton’s method&#xff09;是一种常用的优化算法&#xff0c;在机器学习中被广泛应用于求解函数的最小值。其基本思想是利用二次泰勒展开将目标函数近似为一个二次函数&#xff0c;并用该二次函数来指导搜索方向和步长的选择。 牛顿法需要计…

从零开始学架构——FMEA故障模式与影响分析

1 FMEA介绍 FMEA&#xff08;Failure mode and effects analysis&#xff0c;故障模式与影响分析&#xff09;又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等&#xff0c;专栏采用“故障模式与影响分析”&#xff0c;因为这个中文翻译更加符合可用性的语…

高并发的程序设计-系统设计层面

高并发的程序设计-系统设计层面 目录概述需求&#xff1a; 设计思路实现思路分析1.主要指标二、处理高并发的方案 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better …

ChatGPT写文章效果-ChatGPT写文章原创

ChatGPT写作程序&#xff1a;让文案创作更轻松 在当前数字化的时代&#xff0c;营销推广离不开文案创作。然而&#xff0c;写作对许多人来说可能是一项耗时而枯燥的任务。如果您曾经为写出较高质量的文案而苦恼过&#xff0c;那么ChatGPT写作程序正是为您而设计的。 ChatGPT是…

gitlab安装与使用(图文详解超详细)

一 找最新的安装镜像 推荐用清华源 目前最新版本是15.95 二 在/opt 下创建gitlab文件夹 [rootlocalhost ~]# mkdir /opt/gitlab [rootlocalhost ~]# 三 在gitlab目录下写一个 shell脚本 vim int.sh给它加上执行权限 chmod ux int.sh运行这个脚本 ./ins.sh出现这个截图 安…

three.js之摄像机

本节将在上一节的基础上进一步介绍一下摄像机功能。 three.js的摄像机主要包括两类&#xff1a;正交投影摄像机和透视投影摄像机。 透视投影摄像机&#xff1a;THREE.PerspectiveCamera&#xff0c;最自然的视图&#xff0c;距离摄像机越远&#xff0c;它们就会被渲染得越小。…

【编程问题】解决 mapper.xml 文件的 resultType 爆红问题:Cannot resolve symbol ‘xxx‘

解决mapper.xml文件的resultType爆红问题&#xff1a;Cannot resolve symbol xxx 1.问题描述2.问题分析3.问题解决3.1 配置注解&#xff08;推荐&#xff09;3.2 配置全类名3.3 删除插件 4.事件感悟 系统&#xff1a;Win10 JDK&#xff1a;1.8.0_333 IDEA&#xff1a;2022.2.4 …

HCIP之MPLS中的VPN

目录 HCIP之MPLS中的VPN 定义 例图 解读 流程 双层标签技术 控制层面 数据层面 配置 HCIP之MPLS中的VPN 定义 VPN --- 虚拟网专用 --- 是一种运营商提供的&#xff0c;专门解决虚拟专线安全及宽带问题的综合解决方案 例图 解读 站点 --- 可以理…

Day936.如何重构过大类 -系统重构实战

如何重构过大类 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于如何重构过大类的内容。 在过去的代码里一定会遇到一种典型的代码坏味道&#xff0c;那就是“过大类”。 在产品迭代的过程中&#xff0c;由于缺少规范和守护&#xff0c;单个类很容易急剧膨胀&#…

婴儿尿布台出口美国CPC认证

什么是尿布台&#xff1f;尿布台上架亚马逊要怎么做&#xff1f;咱们接着往下看。 什么是尿布台&#xff1f; 尿布台&#xff1a;尿布台是一种自立式抬升结构&#xff0c;通常设计用于承托体重不超过 13.61 千克&#xff08;30 磅&#xff09;的儿童。儿童采用平躺姿势&#…

Forest-声明式HTTP客户端框架-集成到SpringBoot实现调用第三方restful api并实现接口数据转换

场景 Forest 声明式HTTP客户端API框架&#xff0c;让Java发送HTTP/HTTPS请求不再难。它比OkHttp和HttpClient更高层&#xff0c; 是封装调用第三方restful api client接口的好帮手&#xff0c;是retrofit和feign之外另一个选择。 通过在接口上声明注解的方式配置HTTP请求接…

echarts中横坐标显示为time,使用手册

需求&#xff1a; 后端传递&#xff08;两段数据&#xff0c;不同时间间隔&#xff09;的24h实时数据&#xff0c;前端需要根据24小时时间展示&#xff0c;要求&#xff1a;x轴为0-24h&#xff0c;每个两小时一个刻度 误区&#xff1a; 刚开始通过二维数据的形式秒点&#xff…

Python入门到精通12天(迭代器与生成器)

迭代器与生成器 迭代器生成器 迭代器 迭代器是可迭代的对象&#xff0c;即可以进行遍历的对象。列表、字符串、元组、字典和集合这些都是可迭代的对象&#xff0c;都可以进行遍历。 迭代器是一种访问序列元素的方式&#xff0c;它可以通过next()函数逐个返回序列中的元素。并…

mybatis3源码篇(1)——构建流程

mybatis 版本&#xff1a;v3.3.0 文章目录 构建流程SqlSessionFactoryBuilderXMLConfigBuildertypeAliasesElementtypeHandlerElementmapperElementMapperRegistry MappedStatementMapperAnnotationBuilderXMLMapperBuilderMapperBuilderAssistant SqlSessionFactorySqlSession…