14、RPC与gRPC

news/2024/5/8 23:58:28/文章来源:https://blog.csdn.net/qq23001186/article/details/130197269

目录

  • 一、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 mainimport ("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 mainimport ("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 stringerr = 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)).Donereturn call.Error
}
  • interface\service\interface.go
package serviceconst (//统一rpc的注册名SERVICE_NAME = "HelloService"
)type HelloService interface {Hello(request string, response *string) error
}
  • interface\server\main.go:添加接口约束
package mainimport ("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 mainimport ("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 stringif 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 codecimport ("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 codecimport ("fmt""testing""github.com/stretchr/testify/assert"
)type TestStruct struct {F1 stringF2 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 serviceconst (//统一rpc的注册名SERVICE_NAME = "HelloService"
)type HelloService interface {Hello(request string, response *string) error
}
  • json_tcp\server\main.go
package mainimport ("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 mainimport ("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 stringif 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_testimport ("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 -> []bytepbBytes, err := proto.Marshal(str)if should.NoError(err) {fmt.Println(pbBytes)}// []byte -> protobuf -> objectobj := 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 serviceconst (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 0message MiddleAA {  // Level 1message Inner {   // Level 2int64 ival = 1;bool  booly = 2;}}message MiddleBB {  // Level 1message Inner {   // Level 2int32 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.protoservice HelloService{rpc hello (HelloRequest) returns(HelloResponse);
}message HelloRequest{string value = 1;
}message HelloResponse{string value = 1;
}

在这里插入图片描述

  • server:grpc\server\main.go
package mainimport ("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 Serverserver := 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 mainimport ("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为我们生成一个客户端调用服务端的SDKclient := 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.protoservice HelloService{rpc Channel(stream HelloRequest) returns(stream HelloResponse){}
}message HelloRequest{string value = 1;
}message HelloResponse{string value = 1;
}
  • grpc\server\main.go
package mainimport ("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 Serverserver := 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 mainimport ("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为我们生成一个客户端调用服务端的SDKclient := 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.luyixian.cn/news_show_103681.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue项目 解决el-table自适应高度,vue页面不显示多条滚动条,超出的部分让el-table内部出现滚动条(推荐使用第二种解决方案)

一、需求 后台管理系统&#xff1a;最常见的页面都是由—>左侧菜单、头部tabView页签、主体数据渲染页面&#xff08;AppMain&#xff09;&#xff1b;而一般AppMain页面又分为&#xff1a; 搜索区域、table数据&#xff08;分页&#xff09;&#xff0c;可能也会存在底部&a…

QGIS数据可视化学习笔记01——一般的图层样式调整和在地图上添加图表

说明&#xff1a;QGIS数据可视化章节用的数据为QGIS绘制一张地图学习章节的数据&#xff0c;传送门&#xff1a;https://blog.csdn.net/qq_47188967/article/details/130196284 一、 一般的图层样式调整 首先呢&#xff0c;在QGIS中打开上一章节用的数据&#xff0c;界面如下&…

基于飞桨 PaddleVideo 的骨骼行为识别模型 CTR-GCN

main.pysame_seedsparse_argsmain ensemble.pyconfigs 文件夹Joint&#xff08;J&#xff09;的配置文件ctrgcn_fsd_J_fold0.yamlctrgcn_fsd_J_fold1.yaml Joint Angle&#xff08;JA&#xff09;的配置文件ctrgcn_fsd_JA_fold0.yaml paddlevideo 文件夹utils 文件夹__init__.p…

MATLAB符号运算(七) 更新中...

目录 1、实验目的&#xff1a; 2、实验内容&#xff1a; 1、实验目的&#xff1a; 1&#xff09;掌握定义符号对象和创建符号表达式的方法&#xff1b; 2&#xff09;掌握符号运算基本命令和规则&#xff1b; 3&#xff09;掌握符号表达式的运算法则以及符号矩阵运算&#xf…

[JavaEE初阶] 类加载机制

在真正的战争到来之前,尽可能地变得强大吧~ 文章目录 前言1. 类加载1.1 类加载的过程1.2 类加载的时机1.3 双亲委派模型 前言 这个问题是面试经典题,让我们来求甚解吧~ 1. 类加载 1.1 类加载的过程 如下图 加载,找到.class文件,读取文件内容验证,验证.class文件的格式是否…

SQL优化(3):order by优化

MySQL的排序&#xff0c;有两种方式&#xff1a; Using filesort : 通过表的索引或全表扫描&#xff0c;读取满足条件的数据行&#xff0c;然后在排序缓冲区sort buffer中完成排序操作&#xff0c;所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。 Using index :…

Spring lettuce读写分离

Redis 的 Sentinel 模式默认配置下 Redis 的客户端只对 Master 读写&#xff0c;另外2个Slave闲置。若主从节点在不同机房&#xff0c;在读取时会有跨机房的网络时延&#xff0c;并且比同机房访问更容易发生网络丢包。故在一些场景可以考虑将跨机房的服务节点设置为读写分离 Re…

Python中 re.findAll()、re.sub()、set()的使用

1. re.findall() re.findall()&#xff1a;函数返回包含所有匹配项的列表。返回string中所有与pattern相匹配的全部字串&#xff0c;返回形式为list / 数组。 由函数原型代码可知&#xff0c;findall() 函数存在三个参数&#xff1a; 1. pattern&#xff1a;正则表达式中的 ‘模…

Opencv+Python笔记(五)图像阈值化处理

图像阈值化可以理解为一个简单的图像分割操作&#xff0c;阈值又称为临界值&#xff0c;它的目的是确定出一个范围&#xff0c;然后这个范围内的像素点使用同一种方法处理&#xff0c;而阈值之外的部分则使用另一种处理方法或保持原样。 阈值处理有2种方式&#xff0c;一种是固…

订单交期迟滞,销售回应慢,怎么解决客户问题?

按客户定制产品订单&#xff0c;进行报价和生产的制造企业&#xff0c;有拆解图纸生成物料BOM的工序&#xff0c;通常由企业产品设计部门的拆图员岗位专门负责。 手工制作BOM数据&#xff0c;准确性低 拆图员肉眼查看每页图纸中的表格数据&#xff0c;手动敲键盘填入到企业要…

Android之 颜色选择器

一&#xff0c;简介 1.1 计算机的颜色通常有两种表示方式&#xff1a; 光源模式RGB(Red红, Green绿, Blue蓝)&#xff0c;数值0-255 印刷模式CMYK(Cyan青, Magenta品红, Yellow黄, Black黑)&#xff0c;数值1-100 任何颜色都是由RGB或CMYK混合出来的&#xff0c;再加上透明度…

【HTML+CSS+JS】登录注册页面大合集

前言 学JS也学了一段时间&#xff0c;正巧碰上了人工智能要调用人脸识别接口进行真人人脸识别&#xff0c;于是便萌生了用人脸来进行注册和登录的想法&#xff0c;这样的话就需要开发一个登录注册页面&#xff0c;然后用JS绑定注册事件调用人脸识别接口进行登录注册 饭要一口一…

【数据结构:线性表】单链表

在学习了顺序表&#xff0c;我们可能会对其有一些思考&#xff1a; 中间/头部的插入删除&#xff0c;时间复杂度为O(N)增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间。会有不小的消耗。增容一般是呈2倍的增长&#xff0c;势必会有一定的空间浪费。例如当前容…

【校招VIP】面试了一个抽奖的项目,我终于搞明白了,是8股文终于开始作恶了

最近因为招实习生&#xff0c;进行了很多次面试。 但面试的结果不尽人意。 就感觉今年的面试跟以前差距太大了。 直到经过这个同学的面试&#xff0c;我终于明白了是什么原因。 这个同学是南京一所211的研究生&#xff0c;他的项目经历是做了一个抽奖的微服务管理平台。 也…

10、Mysql常见面试题

Mysql常见面试题 文章目录 Mysql常见面试题一 Mysql索引001 Mysql如何实现的索引机制&#xff1f;002 InnoDB索引与MyISAM索引实现的区别是什么&#xff1f;003 一个表中如果没有创建索引&#xff0c;那么还会创建B树吗&#xff1f; 004 说一下B树索引实现原理&#xff08;数据…

2023移动云大会 | “六大”服务承诺 全力做优“心级服务”

4月25日&#xff0c;以“云擎未来 智信天下”为主题的2023移动云大会在苏州金鸡湖国际会议中心举办&#xff0c;众多政府领导、院士专家、知名企业客户与合作伙伴高层等数千名嘉宾齐聚一堂。 大会期间&#xff0c;移动云深入践行“为国建云”的使命&#xff0c;推出“六大”服…

电感知识大全

目录 一、电感的种类 1、共模电感 2、差模电感 3、工字电感 功率电感 4、磁珠 5、变压器 6、R棒电感、棒形电感、差模电感 二、电感符号 三、电感特性 前面在学习电容的时候&#xff0c;为了让大家更形象&#xff0c;更通俗的去理解这个元器件&#xff0c;都是拿水缸去…

【Vue 移动端开发】适配百分之99的屏幕方案

之前提起移动端适配&#xff0c;都是一些视口的概念&#xff0c;包括物理像素和逻辑像素&#xff0c;理想视口&#xff0c;dpr等等等。利用 media query 和 rem 是最常见的移动端适配方案。如下代码&#xff1a; const deviceWidth document.documentElement.clientWidth || …

为什么很多程序员不反感加班?行内人:老板给钱是真的给啊

为什么很多程序员不反感加班&#xff1f;行内人&#xff1a;说给钱老板真的给&#xff01; 一提到程序员&#xff0c;大部分人第一反应是加班多、996、脱发&#xff0c;这几乎成了外界对程序员刻板印象的标配。不少知名的互联网大厂也是加班之风盛行&#xff0c;譬如著名的华为…

论文阅读:Heterogeneous Graph Contrastive Learning for Recommendation(WSDM ’23)

论文链接 Motivation&#xff1a; 在推荐系统中&#xff0c;图神经网络在建模图结构数据上已经变成一个强有力的工具。但是现实生活的推荐语义通常涉及异质关系&#xff08;像用户的社交关系&#xff0c;物品知识关系的依赖&#xff09;&#xff0c;这些都包含丰富的语义信息…