1. 远程过程调用协议
1.1 定义
远程过程调用(Remote Procedure Call,PRC是一种进程间通信技术,它使得程序可以像调用本地函数一样调用远程服务器上的函数。RPC 屏蔽了底层的通信细节,让开发者能够更专注于业务逻辑,而无需关心网络编程的复杂性。其流程如下:
1.2 grpc
gRPC是由 Google 开发的一个高性能、开源的 RPC 框架,基于 HTTP/2 协议并使用Protocol Buffers(protobuf)作为接口描述语言。
2. Protocol Buffers
Protocol Buffers(Protobuf)是由Google开发的一种语言中立、平台中立、可扩展的序列化数据格式。它用于在不同的程序和系统之间进行高效的数据交换。Protobuf 允许开发者定义数据结构(消息)并自动生成代码来处理这些数据结构的序列化和反序列化。Protocol Buffers具有以下优点:
- 高效性:Protobuf 使用紧凑的二进制格式,比传统的文本格式(如 XML 或 JSON)更节省空间和带宽,序列化和反序列化速度快,适合高性能需求的场景。
- 跨语言支持:Protobuf 支持多种编程语言,包括但不限于 C++, Java, Python, Go, C#, Ruby, JavaScript 等。这种跨语言特性使得它在异构系统中非常有用。
- 可扩展性:Protobuf 允许数据结构的灵活扩展,新的字段可以添加而不会破坏已有的数据格式。这种向后兼容性使得在系统升级和维护过程中更容易处理。
- 自动生成代码:开发者定义数据结构的
.proto
文件后,可以使用 protoc 编译器生成目标语言的代码,减少手工编码的错误和工作量。
2.1 基础语法
Protobuf的定义文件.proto
一般包含语法版本声明、包声明、消息定义和服务定义四个部分。其基本结构如下:
syntax = "proto3";
option go_package="./project/generated"
package example;
message MessageName {//定义消息
// 字段定义
}
service Greeter { //定义服务
}
- 语法版本声明:使用
syntax
指定.proto
文件的语法版本,一般常用的语法版本有:proto2
和proto3
。 - 包声明:
package
关键字用于指定代码的命名空间。 option
中go_pacakge
指定了生成的Go代码应该放置的路径名。go_package
中至少要包含一个/
.- 消息定义:使用
message
关键字声明消息类型。消息类型中存放数据结构。一个消息类型包含一个或多个字段,每个字段有一个唯一的编号。举例如下:
message Person {
string name = 1; // 类型、字段名及其唯一编号。
int32 id = 2;
string email = 3;
}
- 服务定义:使用
service
关键字声明服务类型,服务用于定义 RPC 服务接口。举例如下:
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
3. go环境下的grpc案例
3.1 安装相关的包
先在go语言的project中安装grpc相关的包,具体命令如下
go get google.golang.org/grpc
go get google.golang.org/protobuf
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go get google.golang.org/protobuf/cmd/protoc-gen-go@latest
3.2 编写helloworld.proto文件
如果go环境使用的是GoLand等,需要先安装protobuf
插件。具体过程这里不在赘述。
syntax = "proto3";
option go_package="./generated";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
接着使用protoc
命令将helloworld.proto
文件编译成go语言的源代码。具体命令如下:
protoc --go_out=. --go-grpc_out=. helloworld.proto
其中:
--go_out
指定生成Go代码的输出目录。
--go-grpc_out
指定生成Go gRPC代码的输出目录。
3.3 客户端/服务端代码
服务端代码server.go
package main
import (
"context"
"google.golang.org/grpc"
pb "grpc_test/proto/generated" //引用protoc生成的go package
"log"
"net"
)
type server struct {
pb.UnimplementedGreeterServer
}
//实现sayhello函数
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello, " + req.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端代码client.go
package main
import (
"context"
"flag"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpc_test/proto/generated"
)
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
这里完整的代码结构如下:
然后分别server.go
和client.go
文件(如果是在GoLand中,这两个文件要分别运行在两个终端中),可以看到如下结果:
2024/06/25 20:51:17 Greeting: Hello, world