Golang——gRPC与ProtoBuf介绍

news2024/11/23 23:21:24

一. 安装

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

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

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

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

相关文章

android睡眠分期图

一、效果图 做医疗类项目&#xff0c;经常会遇到做各种图表&#xff0c;本文做的睡眠分期图。 二、代码 引入用到的库 api joda-time:joda-time:2.10.1 调用代码 /*** 睡眠* 分期*/private SleepChartAdapter mAdapter;private SleepChartAttrs mAttrs;private List<SleepI…

day26-单元测试

1. 单元测试Junit 1.1 什么是单元测试&#xff1f;&#xff08;掌握&#xff09; 1.2 Junit的特点&#xff1f;&#xff08;掌握&#xff09; 1.3 基本用法&#xff1a;&#xff08;掌握&#xff09; 实际开发中单元测试的使用方式&#xff08;掌握&#xff09; public class …

安徽京准NTP时钟系统:GPS北斗卫星授时下的生活重塑

安徽京准NTP时钟系统&#xff1a;GPS北斗卫星授时下的生活重塑 安徽京准NTP时钟系统&#xff1a;GPS北斗卫星授时下的生活重塑 时间的流逝自古以来时钟都是人类生活与活动的基础。然而&#xff0c;随着科技的进步&#xff0c;我们对时间管理和测量的方法已经发生了翻天覆地的变…

【UML用户指南】-09-对基本结构建模-类图

目录 1、概述 2、引入 3、过程 4、常用建模技术 4.1、对简单协作建模 4.2、对逻辑数据库模式建模 4.3、正向工程 1、概述 类图是面向对象系统建模中最常见的图。 类图显示一组类、接口、协作以及它们之间的关系 类图用于对系统静态设计视图建模。其大多数涉及到对系统的…

完整指南:远程管理 Linux 服务器的 Xshell6 和 Xftp6 使用方法(Xshell无法启动:要继续使用此程序........,的解决方法)

&#x1f600;前言 在当今软件开发领域&#xff0c;远程管理 Linux 服务器已成为日常工作的重要组成部分。随着团队成员分布在不同的地理位置&#xff0c;远程登录工具的使用变得至关重要&#xff0c;它们为开发人员提供了访问和管理服务器的便捷方式。本文将介绍两款功能强大的…

深度学习框架-----Tensorflow2基础

一、基础概念 1、深度学习框架基础概念 深度学习框架的出现降低了入的槛。我们不在需要丛从复杂的神经网络和反向传播算法开始编代码&#xff0c;可以依据需要&#xff0c;使用已有的模型配置参数&#xff0c;而模型的参数自动训练得到。我们也可以在已有模型的基础上增加自定…

[word] word怎样转换成pdf #职场发展#经验分享#职场发展

word怎样转换成pdf word怎样转换成pdf&#xff1f;word格式是办公中常会用到的格式&#xff0c;word格式编辑好了要想转换成pdf格式再来传输的话需要怎么操作呢&#xff1f;小编这就给大家分享下操作方法&#xff0c;一起来学习下吧&#xff01; 1、安装得力PDF转换器&#x…

vmware将物理机|虚拟机转化为vmware虚机

有时&#xff0c;我们需要从不同的云平台迁移虚拟机、上下云、或者需要将不再受支持的老旧的物理服务器转化为虚拟机&#xff0c;这时&#xff0c;我们可以用一款虚拟机转化工具&#xff1a;vmware vcenter converter standalone&#xff0c;我用的是6.6的版本&#xff0c;当然…

【Linux取经路】信号的发送与保存

文章目录 一、重新理解发送信号二、信号的保存、阻塞信号的概念三、信号集操作函数3.1 sigprocmask3.2 sigpending 四、阻塞信号代码验证五、结语 一、重新理解发送信号 进程通过位图来实现对普通信号&#xff08;1-31号信号&#xff09;的保存&#xff0c;该位图保存在进程的…

DSP28335模块配置模板系列——ADC配置模板

一、配置步骤 1.使能并配置高速时钟HSPCLK、ADC校验 EALLOW;SysCtrlRegs.PCLKCR0.bit.ADCENCLK 1; EDIS;EALLOW;SysCtrlRegs.HISPCP.all ADC_MODCLK; // HSPCLK SYSCLKOUT/(2*ADC_MODCLK)ADC_cal();EDIS; 这里ADC_MODCLK3&#xff0c;所以HSPCLK时钟为150/625Mhz 2.配…

C语言基础——数组(2)

ʕ • ᴥ • ʔ づ♡ど &#x1f389; 欢迎点赞支持&#x1f389; 个人主页&#xff1a;励志不掉头发的内向程序员&#xff1b; 专栏主页&#xff1a;C语言基础&#xff1b; 文章目录 前言 一、二维数组的创建 1.1 二维数组的概念 1.2二维数组的创建 二、二维数组…

javascript导入excel文件

导入文件用到一个 xlsx.core.js 的包。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><script type"tex…

晶振时钟外设总线

如果芯片只有一个时钟信号脚怎么接晶振&#xff1f; 答&#xff1a;如果芯片只有一个时钟信号引脚&#xff0c;说明这是一个时钟输入脚&#xff08;OSC IN&#xff09;&#xff0c;为有源晶振&#xff08;晶体振荡器&#xff09;应用方案&#xff0c;请选择有源晶振并连接其时…

AMD显卡和英伟达显卡哪个好?

显卡是计算机中负责处理图形和视频输出的硬件设备&#xff0c;主要分为两种类型&#xff1a;AMD的A卡和NVIDIA的N卡。那么AMD显卡和英伟达显卡哪个好&#xff1f;怎么选&#xff1f; 答&#xff1a;不能一概而论地说哪个好&#xff0c;因为它们各有优势&#xff0c;选择应基于…

echaerts图例自动滚动并隐藏翻页按钮

效果图 代码 legend: {itemHeight: 14,itemWidth: 14,height: "300", //决定显示多少个// 通过 CSS 完全隐藏翻页按钮pageButtonItemGap: 0,pageButtonPosition: end,pageIconColor: transparent, // 隐藏翻页按钮pageIconInactiveColor: transparent, // 隐藏翻页按…

2024050501-重学 Java 设计模式《实战命令模式》

重学 Java 设计模式&#xff1a;实战命令模式「模拟高档餐厅八大菜系&#xff0c;小二点单厨师烹饪场景」 一、前言 持之以恒的重要性 初学编程往往都很懵&#xff0c;几乎在学习的过程中会遇到各种各样的问题&#xff0c;哪怕别人那运行好好的代码&#xff0c;但你照着写完…

【计算机网络基础知识】

首先举一个生活化的例子&#xff0c;当你和朋友打电话时&#xff0c;你可能会使用三次握手和四次挥手的过程进行类比&#xff1a; 三次握手&#xff08;Three-Way Handshake&#xff09;&#xff1a; 你打电话给朋友&#xff1a;你首先拨打你朋友的电话号码并等待他接听。这就…

LeetCode刷题之HOT100之组合总和

2024/6/3 周一&#xff0c;工作日的第一天。昨晚梦到被导师说去实验室不积极哈哈哈&#xff0c;风扇开到二级&#xff0c;早上被吹醒。买的书马上快要到了。上午刚来准备刷题&#xff0c;结果去搞了一下数据库sql&#xff0c;做的差不多了&#xff0c;还差点格式转换就差不多出…

【WP】猿人学_13_入门级cookie

https://match.yuanrenxue.cn/match/13 抓包分析 抓包分析发现加密参数是cookie中有一个yuanrenxue_cookie 当cookie过期的时候&#xff0c;就会重新给match/13发包&#xff0c;这个包返回一段js代码&#xff0c;应该是生成cookie的 <script>document.cookie(y)(u)(a…

【Text2SQL】评估 LLM 的 Text2SQL 能力

论文&#xff1a;Evaluating the Text-to-SQL Capabilities of Large Language Models ⭐⭐⭐⭐ arXiv:2204.00498 一、论文速读 本论文尝试了多种 prompt 结构&#xff0c;并且评估了他们在 Codex 和 GPT-3 上的表现。下面介绍这些 prompt 结构&#xff1a; 二、不同的 prom…