锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

浅学一下grpc吧

时间:2023-08-04 08:37:00 1004tk2系列圆形连接器

grpc

初识protobuffer

go get -u github.com/golang/protobuf/protoc-gen-go 

第一个proto

proto所有文件都需要.proto作为后缀。

下面是一个简单的protobuffer,name = 1不是指他的价值是1,而是指他在Person中的序号是1.

option go_package = “path;package”

path代表生成的go当前目录中的文件,package代表包为first。

syntax = "proto3"; option go_package="./;first";  package first;  message Person{   string name = 1;   int32 age = 2;   string email = 3;   enum status{     CHILD=0;     ADULT=1;     OLD = 2;   } } 

生成go文件

protoc --go_out= . ./*.proto

生成的pb.go文件

// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.0 // protoc v3.20.0 // source: person.proto  package first  import (  protoreflect "google.golang.org/protobuf/reflect/protoreflect"  protoimpl "google.golang.org/protobuf/runtime/protoimpl"  reflect "reflect"  sync "sync" )  const (  // Verify that this generated code is sufficiently up-to-date.  _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)  // Verify that runtime/protoimpl is sufficiently up-to-date.  _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) )  type PersonStatus int32  const (  Person_CHILD PersonStatus = 0  Person_ADULT PersonStatus = 1  Person_OLD   PersonStatus = 2 )  // Enum value maps for PersonStatus. var (  PersonStatus_name = map[int32]string{ 
           0: "CHILD",   1: "ADULT",   2: "OLD",  }  PersonStatus_value = map[string]int32{ 
           "CHILD": 0,
		"ADULT": 1,
		"OLD":   2,
	}
)

func (x PersonStatus) Enum() *PersonStatus { 
        
	p := new(PersonStatus)
	*p = x
	return p
}

func (x PersonStatus) String() string { 
        
	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}

func (PersonStatus) Descriptor() protoreflect.EnumDescriptor { 
        
	return file_person_proto_enumTypes[0].Descriptor()
}

func (PersonStatus) Type() protoreflect.EnumType { 
        
	return &file_person_proto_enumTypes[0]
}

func (x PersonStatus) Number() protoreflect.EnumNumber { 
        
	return protoreflect.EnumNumber(x)
}

// Deprecated: Use PersonStatus.Descriptor instead.
func (PersonStatus) EnumDescriptor() ([]byte, []int) { 
        
	return file_person_proto_rawDescGZIP(), []int{ 
        0, 0}
}

type Person struct { 
        
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name  string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	Age   int32  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
	Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
}

func (x *Person) Reset() { 
        
	*x = Person{ 
        }
	if protoimpl.UnsafeEnabled { 
        
		mi := &file_person_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *Person) String() string { 
        
	return protoimpl.X.MessageStringOf(x)
}

func (*Person) ProtoMessage() { 
        }

func (x *Person) ProtoReflect() protoreflect.Message { 
        
	mi := &file_person_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil { 
        
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil { 
        
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use Person.ProtoReflect.Descriptor instead.
func (*Person) Descriptor() ([]byte, []int) { 
        
	return file_person_proto_rawDescGZIP(), []int{ 
        0}
}

func (x *Person) GetName() string { 
        
	if x != nil { 
        
		return x.Name
	}
	return ""
}

func (x *Person) GetAge() int32 { 
        
	if x != nil { 
        
		return x.Age
	}
	return 0
}

func (x *Person) GetEmail() string { 
        
	if x != nil { 
        
		return x.Email
	}
	return ""
}

var File_person_proto protoreflect.FileDescriptor

var file_person_proto_rawDesc = []byte{ 
        
	0x0a, 0x0c, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05,
	0x66, 0x69, 0x72, 0x73, 0x74, 0x22, 0x6d, 0x0a, 0x06, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12,
	0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
	0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
	0x52, 0x03, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03,
	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x27, 0x0a, 0x06, 0x73,
	0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x48, 0x49, 0x4c, 0x44, 0x10, 0x00,
	0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x55, 0x4c, 0x54, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x4f,
	0x4c, 0x44, 0x10, 0x02, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x3b, 0x66, 0x69, 0x72, 0x73, 0x74,
	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_person_proto_rawDescOnce sync.Once
	file_person_proto_rawDescData = file_person_proto_rawDesc
)

func file_person_proto_rawDescGZIP() []byte { 
        
	file_person_proto_rawDescOnce.Do(func() { 
        
		file_person_proto_rawDescData = protoimpl.X.CompressGZIP(file_person_proto_rawDescData)
	})
	return file_person_proto_rawDescData
}

var file_person_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_person_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_person_proto_goTypes = []interface{ 
        }{ 
        
	(PersonStatus)(0), // 0: first.Person.status
	(*Person)(nil),    // 1: first.Person
}
var file_person_proto_depIdxs = []int32{ 
        
	0, // [0:0] is the sub-list for method output_type
	0, // [0:0] is the sub-list for method input_type
	0, // [0:0] is the sub-list for extension type_name
	0, // [0:0] is the sub-list for extension extendee
	0, // [0:0] is the sub-list for field type_name
}

func init() { 
         file_person_proto_init() }
func file_person_proto_init() { 
        
	if File_person_proto != nil { 
        
		return
	}
	if !protoimpl.UnsafeEnabled { 
        
		file_person_proto_msgTypes[0].Exporter = func(v interface{ 
        }, i int) interface{ 
        } { 
        
			switch v := v.(*Person); i { 
        
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	type x struct{ 
        }
	out := protoimpl.TypeBuilder{ 
        
		File: protoimpl.DescBuilder{ 
        
			GoPackagePath: reflect.TypeOf(x{ 
        }).PkgPath(),
			RawDescriptor: file_person_proto_rawDesc,
			NumEnums:      1,
			NumMessages:   1,
			NumExtensions: 0,
			NumServices:   0,
		},
		GoTypes:           file_person_proto_goTypes,
		DependencyIndexes: file_person_proto_depIdxs,
		EnumInfos:         file_person_proto_enumTypes,
		MessageInfos:      file_person_proto_msgTypes,
	}.Build()
	File_person_proto = out.File
	file_person_proto_rawDesc = nil
	file_person_proto_goTypes = nil
	file_person_proto_depIdxs = nil
}

序列化和反序列化

package main

import (
	"fmt"
	"github.com/golang/protobuf/proto"
)

func main() { 
        
	p := &Person{ 
        Name: "phm",Age: 12,Email: "8888888@qq.com"}
	//序列化
    marshal, _ := proto.Marshal(p)
	fmt.Println(marshal)
	newP := &Person{ 
        }
	//反序列化
	_ = proto.Unmarshal(marshal, newP)
	fmt.Println(newP)
}

proto和json的相互转换

首先安装一个包

go get google.golang.org/protobuf/encoding/protojson
package main

import (
	"fmt"
	"google.golang.org/protobuf/encoding/protojson"
)

func main() { 
        
	p := &Person{ 
        
		Name:  "phm",
		Age:   13,
		Email: "999999@qq.com",
	}
	format := protojson.Format(p.ProtoReflect().Interface())
	fmt.Println(format)
	message := p.ProtoReflect().Interface()
	_ = protojson.Unmarshal([]byte(format), message)
	fmt.Println(message)
}

在protobuffer中定义服务

如果你想在RPC(远程过程调用)系统中使用你的消息类型,你可以在一个.proto文件中定义一个RPC服务接口,并且Protocol Buffers编译器将以你选择的语言生成服务接口代码和存根。因此,例如,如果你想定义一个RPC服务,它的方法接受你的SeanchRequest并返回一个SeanchResponse,你可以在你的.proto文件中定义它,如下所示:

service RpcService{
  rpc Search(Person) returns (Person);
  rpc register(Person) returns (Person);
}

初识rpc

在上述本地过程调用的例子中,我们是在一台计算机上执行了计算机上的程序,完成调用。随着计算机技术的发展和需求场景的变化,有时就需要从一台计算机上执行另外一台计算机上的程序的需求,因此后来又发展出来了RPC技术。特别是目前随着互联网技术的快速迭代和发展,用户和需求几乎都是以指数式的方式在高速增长,这个时候绝大多数情况下程序都是部署在多台机器上,就需要在调用其他物理机器上的程序的情况。

RPC是Remote Procedure Call Protocol单词首字母的缩写,简称为:RPC,翻译成中文叫远程过程调用协议。所谓远程过程调用,通俗的理解就是可以在本地程序中调用运行在另外一台服务器上的程序的功能方法。这种调用的过程跨越了物理服务器的限制,是在网络中完成的,在调用远端服务器上程序的过程中,本地程序等待返回调用结果,直到远端程序执行完毕,将结果进行返回到本地,最终完成一次完整的调用。

需要强调的是:远程过程调用指的是调用远端服务器上的程序的方法整个过程。

rpc的组成

RPC技术在架构设计上有四部分组成,分别是:客户端、客户端存根、服务端、服务端存根。

这里提到了客户端服务端的概念,其属于程序设计架构的一种方式,在现代的计算机软件程序架构设计上,大方向上分为两种方向,分别是:B/S架构C/S架构。B/S架构指的是浏览器到服务器交互的架构方式,另外一种是在计算机上安装一个单独的应用,称之为客户端,与服务器交互的模式。

由于在服务的调用过程中,有一方是发起调用方,另一方是提供服务方。因此,我们把服务发起方称之为客户端,把服务提供方称之为服务端。以下是对RPC的四种角色的解释和说明:

  • **客户端(Client):**服务调用发起方,也称为服务消费者。
  • **客户端存根(Client Stub):**该程序运行在客户端所在的计算机机器上,主要用来存储要调用的服务器的地址,另外,该程序还负责将客户端请求远端服务器程序的数据信息打包成数据包,通过网络发送给服务端Stub程序;其次,还要接收服务端Stub程序发送的调用结果数据包,并解析返回给客户端。
  • **服务端(Server):**远端的计算机机器上运行的程序,其中有客户端要调用的方法。
  • **服务端存根(Server Stub):**接收客户Stub程序通过网络发送的请求消息数据包,并调用服务端中真正的程序功能方法,完成功能调用;其次,将服务端执行调用的结果进行数据处理打包发送给客户端Stub程序。

rpc的原理

了解完了RPC技术的组成结构我们来看一下具体是如何实现客户端到服务端的调用的。实际上,如果我们想要在网络中的任意两台计算机上实现远程调用过程,要解决很多问题,比如:

  • 两台物理机器在网络中要建立稳定可靠的通信连接。
  • 两台服务器的通信协议的定义问题,即两台服务器上的程序如何识别对方的请求和返回结果。也就是说两台计算机必须都能够识别对方发来的信息,并且能够识别出其中的请求含义和返回含义,然后才能进行处理。这其实就是通信协议所要完成的工作。

让我们来看看RPC具体是如何解决这些问题的,RPC具体的调用步骤图如下:

RPC调用步骤图

在上述图中,通过1-10的步骤图解的形式,说明了RPC每一步的调用过程。具体描述为:

  • 1、客户端想要发起一个远程过程调用,首先通过调用本地客户端Stub程序的方式调用想要使用的功能方法名;
  • 2、客户端Stub程序接收到了客户端的功能调用请求,将客户端请求调用的方法名,携带的参数等信息做序列化操作,并打包成数据包。
  • 3、客户端Stub查找到远程服务器程序的IP地址,调用Socket通信协议,通过网络发送给服务端。
  • 4、服务端Stub程序接收到客户端发送的数据包信息,并通过约定好的协议将数据进行反序列化,得到请求的方法名和请求参数等信息。
  • 5、服务端Stub程序准备相关数据,调用本地Server对应的功能方法进行,并传入相应的参数,进行业务处理。
  • 6、服务端程序根据已有业务逻辑执行调用过程,待业务执行结束,将执行结果返回给服务端Stub程序。
  • 7、服务端Stub程序**将程序调用结果按照约定的协议进行序列化,**并通过网络发送回客户端Stub程序。
  • 8、客户端Stub程序接收到服务端Stub发送的返回数据,**对数据进行反序列化操作,**并将调用返回的数据传递给客户端请求发起者。
  • 9、客户端请求发起者得到调用结果,整个RPC调用过程结束。

go实现rpc

在Go语言官方网站的pkg说明中,提供了官方支持的rpc包,具体链接如下:https://golang.org/pkg/net/rpc/。官方提供的rpc包完整的包名是:net/rpc。根据官方的解释,rpc包主要是提供通过网络访问一个对象方法的功能。

本节课,我们就来学习如何使用go语言官方提供的RPC包实现RPC调用编码。

服务定义和暴露

func (t *T) MethodName(request T1,response *T2) error

上述代码是go语言官方给出的对外暴露的服务方法的定义标准,其中包含了主要的几条规则,分别是:

  • 1、对外暴露的方法有且只能有两个参数,这个两个参数只能是输出类型或内建类型,两种类型中的一种。
  • 2、方法的第二个参数必须是指针类型。
  • 3、方法的返回类型为error。
  • 4、方法的类型是可输出的。
  • 5、方法本身也是可输出的。

我们举例说明:假设目前我们有一个需求,给出一个float类型变量,作为圆形的半径,要求通过RPC调用,返回对应的圆形面积。具体的编程实现思路如下:

type MathUtil struct{ 
        
}
//该方法向外暴露:提供计算圆形面积的服务
func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error { 
        
    *resp = math.Pi * req * req //圆形的面积 s = π * r * r
    return nil //返回类型
}

注册服务和监听

package main

import (
	"math"
	"net"
	"net/http"
	"net/rpc"
)
type MathUtil struct { 
        
}

func (m *MathUtil)mathFunc(req float32,resp *float32)error{ 
        
	*resp = math.Pi * req * req
	return nil
}
func main() { 
        
	//初始化指针类型
	m := new(MathUtil)
	//注册服务
	err := rpc.Register(m)
	if err != nil{ 
        
		panic(err.Error())
	}
	//3、通过该函数把mathUtil中提供的服务注册到HTTP协议上,方便调用者可以利用http的方式进行数据传递
	rpc.HandleHTTP()

	//4、在特定端口监听
	listen, err := net.Listen("tcp", ":8081")
	if err != nil{ 
        
		panic(err.Error())
	}
	go http.Serve(listen,nil)
}

经过服务注册和监听处理,RPC调用过程中的服务端实现就已经完成了。接下来需要实现的是客户端请求代码的实现。

客户端调用

上述的调用方法核心在于client.Call方法的调用,该方法有三个参数,第一个参数表示要调用的远端服务的方法名,第二个参数是调用时要传入的参数,第三个参数是调用要接收的返回值。

package main

import (
	"fmt"
	"net/rpc"
)

func main() { 
        
	//连接服务器
	client, err := rpc.DialHTTP("tcp" 

相关文章