GO简单开发grpc

什么是grpc

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

gRPC(全称:g oogle r emote p rocedure 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正确的接收到了服务端的返回信息。

相关推荐
字节旅行者19 分钟前
C++中如何使用STL中的list定义一个双向链表,并且实现增、删、改、查操作
开发语言·数据结构·c++·链表
搞程序的心海21 分钟前
用Scala玩转Flink:从零构建实时处理系统
开发语言·flink·scala
Asthenia041223 分钟前
面试复盘:深入剖析 IOC 容器
后端
x66ccff29 分钟前
[特殊字符] Pandas 常用操作对比:Python 运算符 vs Pandas 函数
开发语言·python·pandas
逆风优雅1 小时前
python 爬取网站图片的小demo
开发语言·python
m0_616188491 小时前
PDF预览-搜索并高亮文本
开发语言·javascript·ecmascript
IT瘾君1 小时前
Java基础:Logback日志框架
java·开发语言·logback
stevenzqzq1 小时前
kotlin中主构造函数是什么
开发语言·python·kotlin
ChinaRainbowSea1 小时前
8. RabbitMQ 消息队列 + 结合配合 Spring Boot 框架实现 “发布确认” 的功能
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
Tttian6221 小时前
Python办公自动化(2)对word&pdf的操作
开发语言·python