GO简单开发grpc

发布于:2025-04-10 ⋅ 阅读:(35) ⋅ 点赞:(0)

什么是grpc

首先我们需要了解,什么是grpc

gRPC(全称:google remote procedure call)是由Google开发的一个高性能、开源的远程过程调用(RPC)框架。它基于 HTTP/2 协议,并且使用 Protocol Buffers(Protobuf)作为接口定义语言,提供了不同系统之间高效、安全的通信方式。gRPC 支持多种语言(包括 Go、C++、Java、Python 等),并且提供了丰富的功能,比如流式处理、双向通信、负载均衡等。

前置条件,环境配置

我们之前简单地讲过关于它的配置:GO语言 使用protobuf-CSDN博客

这里就不过多的赘述了

protobuf语法

我们之前只简单的讲过如何写一个简单的proto文件

这里我们深入一点的讲解它的语法

我们来看一个简单的文档

syntax = "proto3";  // 语法版本

message Person {
  string name = 1;   // 字符串类型字段,编号为 1
  int32 age = 2;     // 32 位整数,编号为 2
  bool is_student = 3; // 布尔类型,编号为 3
}
 

syntax这个用来指定版本,我们现在学的都是proto3的版本

message Person由于我们使用的是go语言,这里我们就根据go的数据类型来讲
它定义了一个类似于go语言的struct结构体,结构体内部定义了数据的类型、名字、编号(编号不是值,只是编号没有赋值给变量)

它的数据类型有

类型 说明
int32 32位整数
int64 64位整数
uint32 无符号32位整数
uint64 无符号64位整数
sint32 有符号32位整数
sint64 有符号64位整数
fixed32 四字节固定长度整数
fixed64 八字节固定长度整数
float 32位浮点数
double 64位浮点数
bool 布尔值
string 字符串
bytes 二进制数据

嵌套

同时,我们也可以在message里面嵌套另一个message 

数组

proto里面定义数组的格式为:repeated 类型 name = 编号
转化为go代码后就是切片,并且长度和容量默认是0,我们都知道的切片存储的数据数量如果超出了容量,那么切片的容量就会翻倍

容量变化 : 0 - 1 - 2 - 4 - 8 - 16 - 32如果容量>1024,那么就不会翻倍,而是*1.25

Map

proto允许键值对的形式,即map,格式为:map<key类型,value类型> name = 编号
转化为go代码后也是这样的,因为go本身就有map类型

server

这个类型非常重要它的格式是:server name{ rpc name(a) returns(b) }
a和b都要是本文件内的message(或通过 import 正确导入的其他 proto 文件中的 message)

通过已有的proto文件生成go文件

这部分我们需要使用的命令是

protoc -I . go_out=. go-grpc_out=. helloworld.proto

指令         作用
-I . 指定搜索当前目录下文件
go_out=. 生成 Protobuf 序列化/反序列化代码,存放在当前目录下
go-grpc_out=. 生成 gRPC 服务代码,存放在当前目录下
helloworld.proto 文件名,可以指定单个也可以使用 *.proto指定所有当前目录下文件

对于生成文件存放的位置这里要注意

它是由两个因素组成的:1 proto文件内的option go_package指定的目录   2 go_out=. go-grpc_out=.命令行指定的目录

假如我的option go_package="a/b/c/d ;pb" 但是go_out=. go-grpc_out=.却指定当前目录下。最终文件生成位置是使用go_out=. go-grpc_out=.命令下创建一个目录d存放生成文件。pb是生成go文件的包的名字

生成的go文件里面都有哪些内容呢?

首先就是我们定义的message,它被转换为了go里面的结构体,还有该结构体的方法

以及server,它被抽象为一个接口,接口下有一些方法(rpc声明的)。我们想在go文件内使用这个接口,就需要定义一个结构体去实现该接口的所有方法。

假设我有一个proto文件

syntax = "proto3";

option go_package = ".;pb";
service Greeter{
    rpc SayHi(One) returns (Two);
}

message One{
    string name = 1;
    int64 Age = 2;
}

message Two{
    string Return = 1;
}


这里要注意:使用protoc -I . go_out=. go-grpc_out=. helloworld.proto会生成两个文件,一个是helloworld.pb.go另一个是helloworld_grpc.pb.gp

两个文件存储的内容是不同的

helloworld.pb.go存储

部分 说明 示例
消息结构体 对应 proto 中的 message type Request struct{...}
字段访问方法 Getter 方法 func (x *Request) GetName() string
序列化方法 二进制编解码 func (x *Request) Marshal() ([]byte, error)
工具方法 重置/比较等 func (x *Request) Reset()
描述符 元信息

var file_helloworld_proto_rawDesc

helloworld_grpc.pb.gp存储
部分 说明 示例
客户端接口 调用远程服务的存根 type GreeterClient interface
服务端接口 需要实现的方法 type GreeterServer interface
注册函数 服务端注册 func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer)
方法描述符 RPC 方法元数据 _Greeter_SayHello_Handler
流式接口 流式 RPC 支持 type Greeter_SayHelloServer interface

这个文件里面定义了两个message,当我们生成go文件的时候它会变成struct结构体。Greeter会生成对应的接口,我们可以通过生成的go文件的结构体生成一个新的结构体对象,让这个对象实现接口的方法,这样这个结构体对象就实现了接口

如何实现grpc

我们现在已经有proto文件生成的go文件了,我们再来看如何实现服务端和客户端。

我们的想法是:客户端自己没有实现某方法的函数,但是可以调用服务端的函数进行操作

大致的过程是:

client调用server的服务,server完成处理后再返回给client

服务端

我们来看server的编写,这里我们依旧采用上面生成的go文件

首先我们需要有一个服务端的实例

我们使用:g := grpc.NewServer()

然后我们需要注册服务到这个服务端内,那我们注册的服务是什么呢?其实就是实现了接口的结构体

我们首先创建一个结构体

type Server struct {
    pb.UnimplementedGreeterServer
}

这里的pb是我们导入的生成的go文件,它属于这个包。我么就用这个

因为这个结构体要实现接口,接口内存在一个方法SayHi,所以结构体要实现这个方法

func (s *Server) SayHi(ctx context.Context, request *pb.One) (*pb.Two, error) {
    return &pb.Two{
       Return: "nihao" + request.Name + "今年",
    }, nil
}

这里的逻辑是自己写的,但是形参实参都要严格按照我们生成的go文件来写

ctx context.Context这个形参是一定要写并且一定要在第一位的,后面的形参是我们在写proto文件的时候就定义好的。返回值也是,只不过后面多了一个error类型的返回值。

这样,结构体Server就实现了接口,我们就可以把它注册到服务端了

pb.RegisterGreeterServer(g, &Server{})

这个注册的方法也是生成的go文件里面自动生成的。我们直接使用即可

这里的registerGreeterServer也是有说法的,我们在proto文件里面定义的server叫Greeter,所以这里就是RegisterGreeterServer,如果叫Nihao,那就是RegisterNihaoServer

然后我们就开始编写一个监听器,用于监听网络请求

lis, err := net.Listen("tcp", "localhost:8080")

 这里就是内置的net包创建的一个监听器,然后把监听器和注册的服务联系到一起

err = g.Serve(lis)

这样,当监听到对应ip的网络请求的时候就可以跳转到对应的服务了,这样一个简单的服务端就写完了

客户端

我们再来写客户端(发出请求的一方)

这里需要知道的是,它们需要有相同的proto文件以及生成的go文件

在这里就不需要实现什么接口之类的了,我们只需要做:建立连接,发送请求这两步

首先是建立连接:

需要提前导入"google.golang.org/grpc"

conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())

这里的localhost:8080是我们在服务端里面设置的,这样一个grpc的请求就建立完成了

既然有连接,那就需要有关闭连接的时候。我们使用defer conn.Close()关闭连接

c := pb.NewGreeterClient(conn)
r, err := c.SayHi(context.Background(), &pb.One{
    Name: "xxx",
    Age:  12,
})

我们创建一个客户端实例c

我们通过c调用SayHi方法,这个方法虽然比较简单,但是我们如果有能力完全可以换成很复杂很有用的业务逻辑。

r是服务端返回的数据

这样一个客户端就创建好了,我们先运行服务端再运行客户端就可以发现r正确的接收到了服务端的返回信息。