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.proto

package test1

import (
	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 int32

const (
	Test_OK   Test_Status = 0
	Test_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.InternalMessageInfo

func (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.InternalMessageInfo

func (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 FileDescriptorProto
	0x1f, 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 main

import (
	"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()
	//注册HelloService
	hello.RegisterHelloServer(server, HelloService)
	fmt.Println("Listen on" + Addr)

	server.Serve(ls)
}
  • 步骤4:编译客户端

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

package main

import (
	"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)
}
  • 运行:

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

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

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

相关文章

Gitte的使用(Windows/Linux)

Gitte的使用&#xff08;Windows/Linux&#xff09; 一、Windows上使用Gitte1.下载程序2.在Gitte上创建远程仓库3.连接远程仓库4.推送文件到远程仓库 二、Linux上使用Gitte1.第一次从仓库上传1.1生成公钥1.2配置SSH公钥1.3新建一个仓库1.4配置用户名和邮箱在Linux中1.5创建仓库…

在vscode 中使用npm的问题

当我装了 npm和nodejs后 跑项目在 文件中cmd的话可以直接运行但是在 vscode 中运行的时候就会报一下错误 解决方法就是在 vscode 中吧 power shell换成cmd 来运行就行了

Java——简单图书管理系统

前言&#xff1a; 一、图书管理系统是什么样的&#xff1f;二、准备工作分析有哪些对象&#xff1f;画UML图 三、实现三大模块用户模块书架模块管理操作模块管理员操作有这些普通用户操作有这些 四、Test测试类五、拓展 哈喽&#xff0c;大家好&#xff0c;我是无敌小恐龙。 写…

C++输入输出与IO流

C 输入输出与I/O流 文章目录 C 输入输出与I/O流IO类型与基础特性概念与特性IO状态输出缓冲区 文件输入输出文件模式 string流IO处理中常用的函数及操作符综合练习与demo一、 创建文件并写入二、控制台输入数据并拆分存储三、读写电话簿 IO类型与基础特性 C11标准提供了几种IO处…

string经典题目(C++)

文章目录 前言一、最长回文子串1.题目解析2.算法原理3.代码编写 二、字符串相乘1.题目解析2.算法原理3.代码编写 总结 前言 一、最长回文子串 1.题目解析 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 示例 1&#xff1a; 输入&#xff1a;s “babad” 输出&am…

Spring @Transactional 事务注解

一、spring 事务注解 1、实现层(方法上加) import org.springframework.transaction.annotation.Transactional;Transactional(rollbackFor Exception.class)public JsonResult getRtransactional() {// 手动标记事务回滚TransactionAspectSupport.currentTransactionStatus…

# 梯影传媒T6投影仪刷机方法及一些刷机工具链接

梯影传媒T6投影仪刷机方法及一些刷机工具链接 文章目录 梯影传媒T6投影仪刷机方法及一些刷机工具链接1、安装驱动程序2、备份设备rom【boot、system】3、还原我要刷进设备的rom【system】4、打开开发者模式以便于安装apk5、root设备6、更多好链接&#xff1a; 梯影传媒T6使用的…

【嵌入式】波特率9600,发送8个字节需要多少时间,如何计算?

问题&#xff1a; 波特率9600&#xff0c;发送 01 03 00 00 00 04 44 09 (8字节) 需要多少时间&#xff0c;如何计算&#xff1f; 在计算发送数据的时间时&#xff0c;首先要考虑波特率以及每个字符的数据格式。对于波特率9600和标准的UART数据格式&#xff08;1个起始位&…

预期值与实际值对比

编辑实际值和预期值变量 因为在单独的代码当中&#xff0c;我们先定义了变量str&#xff0c;所以在matcher时传入str参数&#xff0c;但当我们要把这串代码写在testrun当中&#xff0c;改下传入的参数&#xff0c;与excel表做连接 匹配的结果是excel表中的expect结果&#xf…

质量小议38 -- 60岁退休的由来

总是要有个标准&#xff0c;质量更是如些。 标准不是固定不变的&#xff0c;与时俱进。 关键词&#xff1a;当时的人均寿命&#xff1b;渐进式 60岁退休。 22大学毕业开始工作&#xff08;当然可能会更早&#xff09;&#xff0c;到60岁退休&#xff0c;要工作38年。 …

从零入手人工智能(2)——搭建开发环境

1.前言 作为一名单片机工程师&#xff0c;想要转型到人工智能开发领域的道路确实充满了挑战与未知。记得当我刚开始这段旅程时&#xff0c;心中充满了迷茫和困惑。面对全新的领域&#xff0c;我既不清楚如何入手&#xff0c;也不知道能用人工智能干什么。正是这些迷茫和困惑&a…

SpringBoot+Vue体育馆管理系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 角色对应功能 学生管理员 功能截图

(四)React组件、useState

1. 组件 1.1 组件是什么 概念&#xff1a;一个组件就是用户界面的一部分&#xff0c;它可以有自己的逻辑和外观&#xff0c;组件之间可以相互嵌套&#xff0c;也可以复用多次。 组件化开发可以让开发者像搭积木一样构建一个完整的庞大应用 1.2 React组件 在React中&#xf…

java中集合List,Set,Queue,Map

Java SE中的集合框架是一组用于存储和操作对象的类和接口。它提供了丰富的数据结构&#xff0c;可以用于解决各种问题。Java SE中的集合框架包含以下主要类和接口&#xff1a; 一. Collection接口&#xff1a; 是集合框架的根接口&#xff0c;它定义了一些通用的集合操作方法…

kafka-生产者事务-数据传递语义事务介绍事务消息发送(SpringBoot整合Kafka)

文章目录 1、kafka数据传递语义2、kafka生产者事务3、事务消息发送3.1、application.yml配置3.2、创建生产者监听器3.3、创建生产者拦截器3.4、发送消息测试3.5、使用Java代码创建主题分区副本3.6、屏蔽 kafka debug 日志 logback.xml3.7、引入spring-kafka依赖3.8、控制台日志…

推荐云盘哪个好,各有各的优势

选择合适的云盘服务是确保数据安全、便捷分享和高效协作的关键。下面将从多个维度对目前主流的云盘服务进行详细的对比和分析&#xff1a; 速度性能 百度网盘青春版&#xff1a;根据测试&#xff0c;其上传和下载确实不限速&#xff0c;但主要定位是办公人群&#xff0c;适用于…

JavaScript基础(十二)

截取字符串 //对象名.toLowerCase();将字符串转为小写 var strLAOWANG; strstr.toLowerCase(); console.log(str); //对象名.toUpperCase();将字符串转为大写 var str1laowang str1str1.toUpperCase(); console.log(str1); 截取字符串 //方法1&#xff1a;对象名.substr(a,b); …

JS(JavaScript)的引用方式介绍与代码演示

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

android 抓取 logcat 日志的方法

1.找到这个路径 2.然后执行命令&#xff08;adb logcat -v time >.\\logcat.log&#xff09;&#xff0c;开始抓取日志 3.这个时候就可以去操作APP了&#xff0c;复现BUG了。 Ctrlc 结束日志抓取 adb logcat -c 清空旧日志

seerfar选品功能,OZON运营插件工具seerfar

在当今这个数字化、信息化的时代&#xff0c;电子商务的飞速发展使得越来越多的商家开始关注如何更高效地运营自己的在线店铺。其中&#xff0c;选品作为电商运营的重要一环&#xff0c;直接影响着店铺的流量、转化率和利润。在OZON这样的电商平台上&#xff0c;如何快速、准确…