Golang——gRPC与ProtoBuf介绍

一. 安装

        1.1 gRPC简介

  • gRPC由google开发,是一款语言中立,平台中立,开源的远程过程调用系统。
  • gRPC客户端和服务器可以在多种环境中运行和交互,例如用java写一个服务器端,可以用go语言写客户端调用。

        1.2 gRPC与Protobuf介绍

  • 微服务架构中,由于每个服务对应的代码库是独立运行的,无法直接调用,彼此之间的通信就是一个大问题。
  • gRPC可以实现微服务,将大的项目拆分为多个小且独立的业务模块,也就是服务,各个服务将使用高效的protobuf协议进行RPC调用,gRPC默认使用protocol buffers,这个是google开源的一套成熟的结构数据序列化机制,当然也可以使用其它数据结构如JSON。
  • 可以用proto files创建gRPC服务,用message类型来定义方法参数和返回类型。

        1.3 安装gRPC和Protobuf

  • go get google.golang.org/protobuf/proto 安装proto
  • go get google.golang.org/grpc 安装grpc
  • go get google.golang.org/protobuf/cmd/protoc-gen-go 安装好后会在$GOPATH/bin目录下生成protoc-gen-go.exe
  • 但还需要一个protoc.exe,windows平台编译受限,很难自己手动编译,直接去网站下载:https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.0

        分享一个下载好的:

https://pan.baidu.com/s/1T8eJkHib2uPL3gNMCRdZmQ 提取码:s42z

二. gRPC简介

        gRPC是一个高性能,开源,通用的RPC框架,由Google推出,基于HTTP2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务器自动生成可靠的功能库。

        在gRPC客户端可以直接调用不同服务器器上的远程程序,使用姿势看起来就像调用本地程序一样,很容易去构建分布式应用和服务。和很多RPC系统一样,服务器负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接调用需要的服务。客户端和服务端可以分别使用gRPC支持的不同语言实现。

        2.1 主要特性

  • 强大的IDL

        gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似XML,JSON,hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储,通信协议等方面。

  • 多语言支持

        gRPC支持多种语言,能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc,Java版本grpc-java和Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持c,c++,Node.js,Python,Ruby,Objective-C,PHP和C#等语言,grpc-java已经支持Android开发。

  • HTTP2

        gRPC基于HTTP2标准设计,所以相对于其他RPC框架,gRPC带来了更多强大的功能,如双向流,头部压缩,多复用请求等。这些功能给移动设备带来重大益处,如节省带宽,降低TCP链接次数,节省CPU使用和延长电池寿命等。同时,gRPC还能提高了云端服务和web应用性能。gRPC即能够在客户端应用,也能够在服务器应用,从而以透明的方式实现客户端和服务端的通信和简化通信系统的构建。

三. ProtoBuf与Go转换

        Proto文件

syntax = "proto3";
package test;
option go_package="test1";message Test {int32 age = 1;int64 count = 2;double money = 3;float score = 4;string name = 5;bool fat = 6;bytes char = 7;//Status 枚举enum Status {OK = 0;FAIL = 1;}Status status = 8;//Child子结构message Child {string sex = 1;}Child child = 9;map<string, string> dict = 10;
}

通过protoc生成对应语言功能代码:

protoc --go_out=./ test.proto

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: test.protopackage test1import (fmt "fmt"proto "github.com/golang/protobuf/proto"math "math"
)// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package//Status 枚举
type Test_Status int32const (Test_OK   Test_Status = 0Test_FAIL Test_Status = 1
)var Test_Status_name = map[int32]string{0: "OK",1: "FAIL",
}var Test_Status_value = map[string]int32{"OK":   0,"FAIL": 1,
}func (x Test_Status) String() string {return proto.EnumName(Test_Status_name, int32(x))
}func (Test_Status) EnumDescriptor() ([]byte, []int) {return fileDescriptor_c161fcfdc0c3ff1e, []int{0, 0}
}type Test struct {Age                  int32             `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"`Count                int64             `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`Money                float64           `protobuf:"fixed64,3,opt,name=money,proto3" json:"money,omitempty"`Score                float32           `protobuf:"fixed32,4,opt,name=score,proto3" json:"score,omitempty"`Name                 string            `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`Fat                  bool              `protobuf:"varint,6,opt,name=fat,proto3" json:"fat,omitempty"`Char                 []byte            `protobuf:"bytes,7,opt,name=char,proto3" json:"char,omitempty"`Status               Test_Status       `protobuf:"varint,8,opt,name=status,proto3,enum=test.Test_Status" json:"status,omitempty"`Child                *Test_Child       `protobuf:"bytes,9,opt,name=child,proto3" json:"child,omitempty"`Dict                 map[string]string `protobuf:"bytes,10,rep,name=dict,proto3" json:"dict,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`XXX_NoUnkeyedLiteral struct{}          `json:"-"`XXX_unrecognized     []byte            `json:"-"`XXX_sizecache        int32             `json:"-"`
}func (m *Test) Reset()         { *m = Test{} }
func (m *Test) String() string { return proto.CompactTextString(m) }
func (*Test) ProtoMessage()    {}
func (*Test) Descriptor() ([]byte, []int) {return fileDescriptor_c161fcfdc0c3ff1e, []int{0}
}func (m *Test) XXX_Unmarshal(b []byte) error {return xxx_messageInfo_Test.Unmarshal(m, b)
}
func (m *Test) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {return xxx_messageInfo_Test.Marshal(b, m, deterministic)
}
func (m *Test) XXX_Merge(src proto.Message) {xxx_messageInfo_Test.Merge(m, src)
}
func (m *Test) XXX_Size() int {return xxx_messageInfo_Test.Size(m)
}
func (m *Test) XXX_DiscardUnknown() {xxx_messageInfo_Test.DiscardUnknown(m)
}var xxx_messageInfo_Test proto.InternalMessageInfofunc (m *Test) GetAge() int32 {if m != nil {return m.Age}return 0
}func (m *Test) GetCount() int64 {if m != nil {return m.Count}return 0
}func (m *Test) GetMoney() float64 {if m != nil {return m.Money}return 0
}func (m *Test) GetScore() float32 {if m != nil {return m.Score}return 0
}func (m *Test) GetName() string {if m != nil {return m.Name}return ""
}func (m *Test) GetFat() bool {if m != nil {return m.Fat}return false
}func (m *Test) GetChar() []byte {if m != nil {return m.Char}return nil
}func (m *Test) GetStatus() Test_Status {if m != nil {return m.Status}return Test_OK
}func (m *Test) GetChild() *Test_Child {if m != nil {return m.Child}return nil
}func (m *Test) GetDict() map[string]string {if m != nil {return m.Dict}return nil
}//Child子结构
type Test_Child struct {Sex                  string   `protobuf:"bytes,1,opt,name=sex,proto3" json:"sex,omitempty"`XXX_NoUnkeyedLiteral struct{} `json:"-"`XXX_unrecognized     []byte   `json:"-"`XXX_sizecache        int32    `json:"-"`
}func (m *Test_Child) Reset()         { *m = Test_Child{} }
func (m *Test_Child) String() string { return proto.CompactTextString(m) }
func (*Test_Child) ProtoMessage()    {}
func (*Test_Child) Descriptor() ([]byte, []int) {return fileDescriptor_c161fcfdc0c3ff1e, []int{0, 0}
}func (m *Test_Child) XXX_Unmarshal(b []byte) error {return xxx_messageInfo_Test_Child.Unmarshal(m, b)
}
func (m *Test_Child) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {return xxx_messageInfo_Test_Child.Marshal(b, m, deterministic)
}
func (m *Test_Child) XXX_Merge(src proto.Message) {xxx_messageInfo_Test_Child.Merge(m, src)
}
func (m *Test_Child) XXX_Size() int {return xxx_messageInfo_Test_Child.Size(m)
}
func (m *Test_Child) XXX_DiscardUnknown() {xxx_messageInfo_Test_Child.DiscardUnknown(m)
}var xxx_messageInfo_Test_Child proto.InternalMessageInfofunc (m *Test_Child) GetSex() string {if m != nil {return m.Sex}return ""
}func init() {proto.RegisterEnum("test.Test_Status", Test_Status_name, Test_Status_value)proto.RegisterType((*Test)(nil), "test.Test")proto.RegisterMapType((map[string]string)(nil), "test.Test.DictEntry")proto.RegisterType((*Test_Child)(nil), "test.Test.Child")
}func init() { proto.RegisterFile("test.proto", fileDescriptor_c161fcfdc0c3ff1e) }var fileDescriptor_c161fcfdc0c3ff1e = []byte{// 303 bytes of a gzipped FileDescriptorProto0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x91, 0xcd, 0x4b, 0xc3, 0x40,0x10, 0xc5, 0x9d, 0x7c, 0xb5, 0x99, 0x8a, 0xc4, 0xa5, 0x87, 0xb5, 0xa7, 0xa5, 0x07, 0x59, 0x2f,0x05, 0xeb, 0x41, 0xf1, 0xe6, 0x27, 0x88, 0x82, 0x30, 0x7a, 0xf2, 0x16, 0xd3, 0xd5, 0x96, 0xb6,0x89, 0x64, 0xb7, 0x62, 0x8e, 0xfe, 0xe7, 0x32, 0xbb, 0x2a, 0xbd, 0xbd, 0x37, 0xef, 0x17, 0xf2,0x78, 0x8b, 0xe8, 0x8c, 0x75, 0x93, 0x8f, 0xb6, 0x71, 0x8d, 0x48, 0x58, 0x8f, 0xbf, 0x63, 0x4c,0x9e, 0x8d, 0x75, 0xa2, 0xc0, 0xb8, 0x7c, 0x37, 0x12, 0x14, 0xe8, 0x94, 0x58, 0x8a, 0x21, 0xa6,0x55, 0xb3, 0xa9, 0x9d, 0x8c, 0x14, 0xe8, 0x98, 0x82, 0xe1, 0xeb, 0xba, 0xa9, 0x4d, 0x27, 0x63,0x05, 0x1a, 0x28, 0x18, 0xbe, 0xda, 0xaa, 0x69, 0x8d, 0x4c, 0x14, 0xe8, 0x88, 0x82, 0x11, 0x02,0x93, 0xba, 0x5c, 0x1b, 0x99, 0x2a, 0xd0, 0x39, 0x79, 0xcd, 0xff, 0x79, 0x2b, 0x9d, 0xcc, 0x14,0xe8, 0x3e, 0xb1, 0x64, 0xaa, 0x9a, 0x97, 0xad, 0xec, 0x29, 0xd0, 0xbb, 0xe4, 0xb5, 0x38, 0xc2,0xcc, 0xba, 0xd2, 0x6d, 0xac, 0xec, 0x2b, 0xd0, 0x7b, 0xd3, 0xfd, 0x89, 0x6f, 0xce, 0x4d, 0x27,0x4f, 0x3e, 0xa0, 0x5f, 0x40, 0x1c, 0x62, 0x5a, 0xcd, 0x17, 0xab, 0x99, 0xcc, 0x15, 0xe8, 0xc1,0xb4, 0xd8, 0x22, 0xaf, 0xf8, 0x4e, 0x21, 0x16, 0x1a, 0x93, 0xd9, 0xa2, 0x72, 0x12, 0x55, 0xac,0x07, 0xd3, 0xe1, 0x16, 0x76, 0xbd, 0xa8, 0xdc, 0x4d, 0xed, 0xda, 0x8e, 0x3c, 0x31, 0x3a, 0xc0,0xd4, 0x7f, 0xc9, 0x5d, 0xad, 0xf9, 0xf2, 0x9b, 0xe4, 0xc4, 0x72, 0x74, 0x8a, 0xf9, 0x3f, 0xcd,0xf1, 0xd2, 0x74, 0x7f, 0xf1, 0x32, 0xcc, 0xf0, 0x59, 0xae, 0x36, 0xc6, 0x4f, 0x96, 0x53, 0x30,0xe7, 0xd1, 0x19, 0x8c, 0x47, 0x98, 0x85, 0xde, 0x22, 0xc3, 0xe8, 0xf1, 0xbe, 0xd8, 0x11, 0x7d,0x4c, 0x6e, 0x2f, 0xee, 0x1e, 0x0a, 0xb8, 0xec, 0xbd, 0xa4, 0x5c, 0xe6, 0xf8, 0x35, 0xf3, 0x2f,0x73, 0xf2, 0x13, 0x00, 0x00, 0xff, 0xff, 0x80, 0xdf, 0xd8, 0xf5, 0xa7, 0x01, 0x00, 0x00,
}

        3.1 syntax

        使用protoc的版本:

syntax = "proto3";

        3.2 Package

        在proto文件中使用package关键字声明包名,默认go语言中的包名,如果需要指定其它的包名,可以使用go_package选项:

package test;
option go_package="test1";

        3.3 Message

        proto中的message对应go中的struct,全部使用驼峰命名规则。嵌套定义的message,enum转换为go之后,名称变为Parent_Child结构。

如下:

        除了会生成对应的结构外,还会有些工具方法。

        枚举会生成对应名称的常量,同时会有两个map方便使用。

        3.4 Service

         定义一个简单的Service,TestService有一个方法Test,接收一个Request参数,返回Response:

service TestService
{rpc Test(Request) returns(Response){};
}message Request
{string name = 1;
}message Response
{string message = 1;
}

执行命令:

protoc --go_out=plugins=grpc:. test.proto

        生成的go代码中包含该Service定义的接口,客户端接口已经自动实现了,直接提供客户端使用者调用,服务端接口需要由服务提供方实现。 

// TestServiceClient is the client API for TestService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type TestServiceClient interface {Test(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}type testServiceClient struct {cc *grpc.ClientConn
}func NewTestServiceClient(cc *grpc.ClientConn) TestServiceClient {return &testServiceClient{cc}
}func (c *testServiceClient) Test(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {out := new(Response)err := c.cc.Invoke(ctx, "/test.TestService/Test", in, out, opts...)if err != nil {return nil, err}return out, nil
}// TestServiceServer is the server API for TestService service.
type TestServiceServer interface {Test(context.Context, *Request) (*Response, error)
}

 四. Protobuf语法

        4.1 基本规范

  • 文件以.proto作为文件后缀,除结构体之外的语句以分号结尾
  • 结构定义可以包含: message,service,enum
  • rpc方法定义结尾的分号可有可无
  • message命名采用驼峰式命名方式,字段命名采用小写字母加下划线分隔方式
message SongServerRequest
{required string song_name = 1;
}
  • enum采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式
enum Foo
{FIRST_VALUE = 1;SECOND_VALUE = 2;
}
  • service与rpc方法名统一采用驼峰式命名

         4.2 字段规则

  • 字段格式:限定修饰符 | 数据类型 | 字段名称 = 字段编码值 | [字段默认值]
  • 限定修饰符包含 required\optional\repeated
    • required:表示必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃
    • optional:表示是一个可选字段,可选对于发送方,在发送该消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应处理,如果无法识别,则忽略该字段,消息中其它字段正常处理。——因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老版本无需升级程序也可以正常与新的软件进行通信,只不过新字段无法识别而已,因为并不是每一个节点都需要新的功能,因此可以做到按需升级和平滑过渡。
    • repeated:表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作时在传递一个数组的值。
  • 数据类型
    • protobuf定义了一套基本数据类型。几乎都可以映射到C++\JAVA等语言的基础数据类型。

+N表示打包字节不固定,而是根据数据大小和长度。

关于fixed32和int32的区别:fixed32打包效率比int32的效率高,但是使用空间一般比int32多。因此一个时间效率高,一个属于空间效率高。 

  • 字段名称
    • 字段名称的命名与C,C++,Java等语言的变量命名方式几乎相同
    • protobuf建议字段的命名采用以下划线分隔的驼峰式。例如first_name而不是firstName 
  • 字段编码值
    • 有了该值,通信双方才能互相识别对方的字段,相同的编码值,其限定修饰符和数据类型必须相同,编码值的取值范围为1~2^32
    • 其中1~15的编码时间和空间效率是最高的,编码值越大,其编码时间和空间效率就越低,所以建议把经常要传递的值的字段编码设置在1~15之间的值。
    • 1900~2000编码值为Google protobuf系统内部保留值,建议不要在自己的项目中使用 
  • 字段默认值
    • 当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端 

         4.3 service如何定义

  • 如果想要将消息类型用在RPC系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器会根据所选择的不同语言生成服务接口代码
  • 例如:想要定义一个RPC服务并具有一个方法,该方法接收SearchRequest并返回一个SearchResponse,此时可以在.proro文件中进行如下定义:
service SearchService
{rpc Search(SearchRequest) returns (SearchResponse){};
}
  • 生成的接口代码作为客户端与服务端的约定,服务端必须实现定义的所有接口方法,客户端直接调用同名方法向服务器发起请求,比较麻烦的是,即便业务上不需要参数也必须指定一个请求消息,一般会定义一个空message

        4.4 message如何定义

  • 一个message类型定义描述了一个请求或响应的消息格式,可以包含多种类型字段
  • 字段名用小写,转为go文件后自动变为大写,message就相当于结构体
  • 每个字段声明以分号结尾,.proto文件支持双斜杠(//)添加注释
syntax = "proto3"
message SearchRequest
{string query = 1;int32 page_number = 2;int32 result_per_page = 3;
}
  •  添加多个message类型,一个.proto文件中可以定义多个消息类型,一般用于同时定义多个相关信息
  • message支持嵌套使用,作为另一message中的字段类型
message SearchResponse
{repeated Result results = 1;
}message Result
{string url = 1;string title = 2;repeated string snippets = 3;
}
  •  支持嵌套消息,消息可以包含另一个消息作为字段。也可以在消息内定义一个新的消息
  • 内部嵌套的message类型名称只可在内部直接使用
syntax = "proto3";
package test;message A
{message B{int32 i = 1;int32 j = 2;}B b = 1;
}message C
{//B b = 1; 报错test.proto:17:9: "B" is not defined.
}
  • 另外,还可以多层嵌套

        4.5 proto3的Map类型

  • proto3支持map类型声明
syntax = "proto3";
package test;message C
{}message A
{message B{}B b = 1;map<int32, C> m = 2;
}
  • 键,值可以是内置类型,也可以是自定义message类型
  • 字段不支持repeated属性

        4.6 proto文件编译

  • 通过定义好的.proto文件生成Java,Python,C++,Go,Ruby,JavaNano, Objective-C,orC#代码,需要安装编译器protoc
  • 当使用protocol buffer编译器运行.proto文件时,编译器将生成所选语言的代码,用于使用.proto文件中定义的消息类型、服务接口约定等。不同语言生成的代码格式不同:
    • C++:每个.proto文件生成一个.h文件和一个.cc文件,每个消息类型对应一个类
    • Java:生成一个.java文件,同样每个消息对应一个类,同时还有一个特殊的Builder用于创建消息接口
    • Python:姿势不太一样,每个.proto文件中的消息类型生成一个含有静态描述符的模块,该模块与一个元类metaclass在运行时创建需要的Python数据访问类
    • Go:生成一个.pb.go文件,每个消息类型对应一个结构体
    • Ruby:生成一个.rb文件的Ruby模块,包含所有消息类型
    • JavaNano:类似Java,但不包含Builder
    • Objective-C:每个.proto文件生成一个pbobjc.h和一个pbobjc.m文件
    • C#:生成.cs文件包含,每个消息类型对应一个类

protobuf在linux下载编译和使用_protobuf下载-CSDN博客

         4.7 import导入定义

  • 可以使用import语句导入使用其它描述文件中声明的类型
  • protobuf接口文件可以像C语言的.h文件一个,分离为多个,在需要的时候通过import导入需要的文件。其行为和C语言的include大致相同。例如:import "others.proto"
  • protocol buffer 编译器会在-I 或 -proto_path参数指定的目录中查找导入的文件,如果没有指定该参数,默认在当前目录中查找。

注意:import导入使用其它描述文件中的类型,需要两个proto文件的包名相同。

        例子:

  • 在不同的语言中,包名定义对编译后生成的代码的影响不
    • C++ 中:对应C++命名空间,例如Open会在命名空间foo::bar
    • Java 中:package会作为Java包名,除非指定了option jave_package选项
    • Python 中:package被忽略
    • Go 中:默认使用package名作为包名,除非指定了option go_package选项
    • JavaNano 中:同Java
    • C# 中:package会转换为驼峰式命名空间,如Foo.Bar,除非指定了option csharp_namespace选项

         4.8 小案例

        利用gRPC实现一个Hello Service,客户端发送包含字符串名字的请求,服务端返回hello消息。

        流程:

  • 编写.proto描述文件
  • 编译成.pb.go文件
  • 服务端实现约定的接口并提供服务
  • 客户端按照约定调用.pb.go文件中的方法请求服务

        项目结构:

  • 步骤1:编写描述文件:

        hello.proto文件中定义了一个Hello Service,该服务包含一个SayHello方法,同时声明了HelloRequest和HelloResponse消息结构用作请求和响应。客户端使用HelloRequest参数调用SayHello方法请求服务端,服务端响应HelloResponse消息。一个简单的服务就定义好了。

syntax="proto3";
package hello;
option go_package="hello";service Hello
{rpc SayHello(HelloRequest)returns(HelloResponse){}; 
}message HelloRequest
{string name = 1;
}message HelloResponse
{string message = 1;
}
  • 步骤2:生成.pb.go文件

        按照.proto文件中的说明,包含HelloServer描述,客户端接口及实现HelloClient,以及HelloRequest,HelloResponse结构体。 

protoc --go_out=plugins=grpc:.\grpc\proto -I=.\grpc\proto .\grpc\proto\hello.proto
  • 步骤3:编写服务器

         服务端定义了一个空结构来实现了HelloServer接口,实例化gRPC server并注册,开始提供服务。

package mainimport ("context""fmt""net"hello "sample-app/grpc/proto""google.golang.org/grpc"
)var Addr = "127.0.0.1:8080"type helloService struct{}var HelloService = helloService{}func (h helloService) SayHello(c context.Context, req *hello.HelloRequest) (*hello.HelloResponse, error) {resp := new(hello.HelloResponse)resp.Message = fmt.Sprintf("Hello %s", req.Name)return resp, nil
}func main() {ls, err := net.Listen("tcp", Addr)if err != nil {fmt.Println(err)return}//新建一个grpc服务器server := grpc.NewServer()//注册HelloServicehello.RegisterHelloServer(server, HelloService)fmt.Println("Listen on" + Addr)server.Serve(ls)
}
  • 步骤4:编译客户端

        客户端只需要初始化连接,之后调用.pb.go中的SayHello方法,即可向服务端发送请求。使用的姿势就像调用本地接口一样。

package mainimport ("context""fmt"hello "sample-app/grpc/proto""google.golang.org/grpc"
)const (Addr = "127.0.0.1:8080"
)func main() {conn, err := grpc.Dial(Addr, grpc.WithInsecure())if err != nil {fmt.Println(err)return}defer conn.Close()c := hello.NewHelloClient(conn)req := new(hello.HelloRequest)req.Name = "gRPC"resp, err := c.SayHello(context.Background(), req)if err != nil {fmt.Println(err)return}fmt.Println(resp.Message)
}
  • 运行:

        编译客户端和服务器代码,先启动服务器,在启动客户端发送请求。

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

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

相关文章

④-1单细胞学习-cellchat单数据代码补充版

目录 1&#xff0c;数据输入及处理 ①载入包和数据 ②CellChat输入数据准备 ③构建CellChat对象 ④数据预处理 2&#xff0c;细胞通讯预测 ①计算细胞通讯概率 ②提取配受体对细胞通讯结果表 ③提取信号通路水平的细胞通讯表 ④细胞互作关系可视化 1&#xff09;细胞…

IO流(缓冲流)

1.字节缓冲流 原理&#xff1a;字节缓冲输入流自带8KB缓冲池;字节缓冲输出流自带8KB缓冲池 public static void main(String[] args) throws IOException {try(InputStream is new FileInputStream("D:\\pt\\123.jpg");//1.定义一个字节缓冲输入流包装原始的字节输…

Modbus主站和从站的区别

Modbus主站,从站 在工业自动化领域&#xff0c;Modbus是一种常用的通信协议&#xff0c;用于设备之间的数据交换。在Modbus通信中&#xff0c;主站和从站是两个关键的角色。了解主站和从站之间的区别对正确配置和管理Modbus网络至关重要。 Modbus主站的特点和功能 1.通信请求发…

硬盘坏了数据能恢复吗 硬盘数据恢复一般多少钱

在数字化时代&#xff0c;我们的生活和工作离不开电脑和硬盘。然而&#xff0c;硬盘故障是一个常见的问题&#xff0c;可能会导致我们的数据丢失。当我们的硬盘坏了&#xff0c;还能恢复丢失的数据吗&#xff1f;今天我们就一起来探讨关于硬盘坏了数据能恢复吗&#xff0c;硬盘…

Polar Web【困难】上传

Polar Web【困难】上传 Contents Polar Web【困难】上传探索&思路&效果进入环境绕过过程Webshell连接 EXPPayload 总结 探索&思路&效果 本题的主题可见为文件上传&#xff0c;详情在破解的过程中逐步发掘&#xff1a; 进入环境&#xff0c;为一个文件上传功界面…

定个小目标之刷LeetCode热题(14)

了解股票的都知道&#xff0c;只需要选择股票最低价格那天购入&#xff0c;在股票价格与最低价差值最大时卖出即可获取最大收益&#xff0c;总之本题只需要维护两个变量即可&#xff0c;minPrice和maxProfit&#xff0c;收益 prices[i] - minPrice,直接用代码描述如下 class …

vscode中执行python语句dir(torch)不返回结果

输入半天&#xff0c;发现在IDLE运行后的shell界面输入语句就会返回一大串。但是在vscode中老是不返回值。 结果恍然发现这没加print&#xff08;&#xff09;。 无语惨了。 家人们&#xff0c;这是python&#xff0c;而不是matlab。思维还没转换过来&#xff0c;笑死

umap降维,c++用法纪实

全是血泪&#xff0c;可惜对于大量数据&#xff0c;速度还是太慢。 一、代码 // ConsoleApplication2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream>#include "knncolle/knncolle.hpp" #include "Umap.…

[数据集][图像分类]人种黄种人白人黑人等分类数据集56000张7类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;56000 分类类别数&#xff1a;7 类别名称:[“Black”,“East_Asian”,“Ind…

Vue3【十三】watch监视

Vue3【十三】watch监视 Vue3 中的watch祝你能监视以下四种数据 ref 定义的数据reactive定义的数据函数返回一个值一个包含上述内容的数组 案例截图 目录结构 案例代码 Person.vue <template><div class"person"><!-- <h1>Watch情况1&#xff…

C++基础编程100题-008 OpenJudge-1.3-06 甲流疫情死亡率

更多资源请关注纽扣编程微信公众号 http://noi.openjudge.cn/ch0103/06/ 描述 甲流并不可怕&#xff0c;在中国&#xff0c;它的死亡率并不是很高。请根据截止2009年12月22日各省报告的甲流确诊数和死亡数&#xff0c;计算甲流在各省的死亡率。 输入 输入仅一行&#xff…

数据中心运维管理方案

数据中心运维管理方案 随着数据中心在现代信息社会中的重要性日益增加&#xff0c;高效、可靠的运维管理方案成为保障其稳定运行的关键。本文将探讨数据中心运维管理的策略和实践&#xff0c;旨在为运维团队提供全面、系统的管理方法&#xff0c;确保数据中心在任何情况下都能…

Word Split Line

Word Split Line 分割线 https://download.csdn.net/download/spencer_tseng/89413772

小柴带你学AutoSar系列一、基础知识篇(5)makefile基础

Flechazohttps://www.zhihu.com/people/jiu_sheng 小柴带你学AutoSar总目录https://blog.csdn.net/qianshang52013/article/details/138140235?spm=1001.2014.3001.5501

国内docker镜像站全军覆没 如何自己部署一个Docker镜像加速服务器?

近日&#xff0c;在使用SJTUG提供的镜像加速拉取镜像的时候死活拉不下来&#xff0c;不管是 docker hub 还是国内的某些镜像站&#xff0c;同样都无法使用&#xff0c;虽然现在还有部分可用的镜像站&#xff0c;但也说不准某一天因为某些原因同样停止提供了&#xff0c;这时候就…

华为防火墙配置 SSL VPN

前言 哈喽&#xff0c;我是ICT大龙。本期给大家更新一次使用华为防火墙实现SSL VPN的技术文章。 本次实验只需要用到两个软件&#xff0c;分别是ENSP和VMware&#xff0c;本次实验中的所有文件都可以在文章的末尾获取。话不多说&#xff0c;教程开始。 什么是VPN 百度百科解…

Java核心: 类加载器

这一节我们来学习Java的类加载器&#xff0c;以及常用的类加载器实现URLClassLoader。 1. Java类加载器 类加载器用于将字节码读取并创建Class对象。我们知道JVM本身是用C写的&#xff0c;一开始执行的时候由C程序来加载并引导字节码的运行&#xff0c;这些由C编写的加载字节…

[数据集][目标检测]攀墙攀越墙壁数据集VOC格式-701张

数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;701 标注数量(xml文件个数)&#xff1a;701 标注类别数&#xff1a;1 标注类别名称:["fq"] 每个类别标…

htb-window-2-blue-smb

nmap msf 漏洞搜索 配置 获取flag

SpringBoot整合钉钉实现消息推送

前言 钉钉作为一款企业级通讯工具&#xff0c;具有广泛的应用场景&#xff0c;包括但不限于团队协作、任务提醒、工作汇报等。 通过Spring Boot应用程序整合钉钉实现消息推送&#xff0c;我们可以实现以下功能&#xff1a; 实时向指定用户或群组发送消息通知。自定义消息内容…