基础知识准备
在代码实战gRPC之前,我们需要了解一些基础知识:
- RPC(Remote Procedure Call):远程过程调用,是一种通信协议,允许应用程序在不同的计算机上请求服务而不需要了解底层网络细节。
- gRPC:gRPC 是一种高性能、开源的远程过程调用(RPC)框架,由 Google 开发,并基于 HTTP/2、Protocol Buffers 等技术实现。
- Protocol Buffers(ProtoBuf):是一种由 Google 开发的轻量级、高效、可扩展的结构化数据序列化方法,常用于通信协议、数据存储等领域。它采用了二进制格式,相比于 XML、JSON 等文本格式,更加紧凑,速度更快,同时具备了跨语言、跨平台的特性。
4.grpc 将 Protocol Buffers 作为其默认的序列化格式,就说明grpc中的消息通信使用 pro tobuf 格式定义,
5.而grpc 编译器将根据 protobuf 文件生成相应语言的代码,包括消息结构和 RPC 服务接口,但是 protobuf 却不依赖于 gRPC
RPC 开发流程
gRPC 的开发流程一般分为以下几个步骤:
- 定义服务和消息:使用 Protocol Buffers 定义 RPC 服务的接口和消息格式。
- 生成代码:利用 Protocol Buffers 的编译器生成服务端和客户端的代码。
- 实现服务:在服务端实现定义的服务接口。
- 启动服务:启动 gRPC 服务并监听指定端口。
- 调用服务:在客户端调用 gRPC 生成的客户端代码来与服务端进行通信。
应用流程
- 客户端请求:客户端调用本地的 gRPC 方法,传入参数。
- 消息序列化:客户端将参数序列化成 Protocol Buffers 格式的消息。
- HTTP/2 传输:序列化后的消息通过 HTTP/2 协议传输到服务端。
- 消息反序列化:服务端接收到消息后,将其反序列化为相应的参数。
- 处理请求:服务端调用相应的函数处理请求,并生成返回结果。
- 结果序列化:服务端将返回结果序列化成 Protocol Buffers 格式的消息。
- 返回结果:序列化后的结果通过 HTTP/2 协议传输回客户端。
- 消息反序列化:客户端接收到结果后,将其反序列化为相应的数据类型。
- 返回结果:客户端获取到最终的返回结果。
Protocol Buffers 语法详解
1. 定义消息类型
使用 Protobuf,首先要定义消息类型。消息类型是结构化数据的抽象,类似于面向对象编程中的类。
message Person {
required int32 id = 1;
required string name = 2;
optional string email = 3;
}
上面定义了一个 Person
消息类型,它包含了 id
、name
和可选的 email
字段。
required
:表示该字段是必须的。optional
:表示该字段是可选的。int32
、string
:字段的数据类型。id = 1
、name = 2
、email = 3
:字段的唯一标识符,用于在序列化和反序列化时标识字段。
2. 消息字段规则
required
:该字段必须出现一次。optional
:该字段可以出现零次或一次。repeated
:该字段可以重复多次。
3. 默认值
可以为字段指定默认值,当字段未被设置时将采用默认值。
message Person {
required int32 id = 1;
required string name = 2 [default = "John"];
optional string email = 3 [default = "john@example.com"];
}
4. 枚举类型
Protobuf 支持枚举类型,用于定义一组命名的常量值。
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
5. 嵌套消息类型
消息类型可以嵌套定义在其他消息类型内部。
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
message Person {
required int32 id = 1;
required string name = 2;
optional string email = 3;
repeated PhoneNumber phones = 4;
}
RPC结合Protobuf案例实战
场景需求
通过protobuf+grpc实现查询用户信息的服务
proto定义消息的结构和服务的接口
syntax = "proto3";
package example;
option go_package = "./pb"; //这里要注意哈 这里是代表将生成的go服务代码放在哪个目录
message UserRequest {
string id = 1;
}
message UserResponse {
string name = 1;
int32 age = 2;
}
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
}
解析下上面的proto文件
syntax = "proto3";
: 这行指定了使用的 protobuf 版本,这里是 proto3 版本。package example;
: 这个语句指定了包的名称。option go_package = "./pb";
: 指定了生成的输出目录。message UserRequest {
: 定义了一个消息类型,名为 UserRequest,包含一个字符串字段 id,字段标识为 1。message UserResponse {
: 另一个消息类型的定义,名为 UserResponse,包含一个字符串字段 name 和一个整数字段 age,它们分别标识为 1 和 2。service UserService {
: 这里定义了一个服务接口,名为 UserService,包含一个名为 GetUser 的远程过程调用(RPC)方法,输入参数为 UserRequest,返回类型为 UserResponse。
总的来说,这段 protobuf 文件定义了两个消息类型(UserRequest 和 UserResponse), 以及一个包含一个 RPC 方法的服务接口(UserService), 该方法接收 UserRequest 并返回 UserResponse。(我说的很详细,我不怕啰嗦,我只怕你们看不懂)
生成grpc代码
protoc --go_out=. --go-grpc_out=. --plugin=protoc-gen-go-grpc=/你自己的路径/protoc-gen-go-grpc example.proto
如果出现下面这个异常, 但是不要慌:
protoc-gen-go-grpc: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
--go-grpc_out: protoc-gen-go-grpc: Plugin failed with status code 1.
这说明你的protoc-gen-go-grpc 没有安装,或者安装了但是没有配置环境变量
安装:go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
配置环境变量:
1: export PATH=$PATH:/Users/gaoxin/go/bin
2: source ~/.bash_profile
Windows 我不太熟悉,你们百度下。。
RPC服务端
package main
import (
"awesomeProject1/rpc/pb"
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"log"
"net"
)
type server struct{}
func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
// Simulate fetching user data from database
userData := map[string]pb.UserResponse{
"1": {Name: "大美人", Age: 30},
"2": {Name: "小帅哥", Age: 25},
// Add more user data as needed
}
user, ok := userData[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "User not found")
}
return &user, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
log.Println("Server started at :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端 client代码
package main
import (
"awesomeProject1/rpc/pb"
"context"
"google.golang.org/grpc"
"log"
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewUserServiceClient(conn)
userID := "1" //todo 比如我们要获取用户ID 1 这个用户的信息
resp, err := c.GetUser(context.Background(), &pb.UserRequest{Id: userID})
if err != nil {
log.Fatalf("could not call service: %v", err)
}
log.Printf("用户信息: Name - %s, Age - %d", resp.Name, resp.Age)
}
运行grpc服务端
运行grpc客户端获取用户信息
5. 交互流程细节
- 客户端启动,连接到服务器的 gRPC 服务端口。
- 客户端调用
GetUser
方法,并传递用户的 ID。 - 服务端收到请求后,根据传递的用户 ID,从数据库中获取对应用户的信息。
- 服务端将获取到的用户信息构建成
UserResponse
对象,并发送给客户端。 - 客户端接收到服务端返回的用户信息后,打印出用户的姓名和年龄。
最后的心里话
到此,我们通过学习proto文件语法,并生成go的grpc代码,然后创建了一个 gRPC 客户端。并且构造了查询用户信息的请求,并通过客户端调用了远程grpc服务。最终我们处理了服务端返回的响应数据。
Was this helpful?
0 / 0