RPC 笔记(03)— gRPC 概念、安装、编译、客户端和服务端示例
1. gRPC 概念
gRPC
是 Google
开源的一款高性能的 RPC
框架。GitHub
上介绍如下:
gRPC is a modern, open source, high-performance remote procedure call (RPC) framework that can run anywhere.
市面上的 RPC
框架数不胜数,包括 Alibaba Dubbo
和微博的 Motan
等。gRPC
能够在众多的框架中脱颖而出,是跟其高性能是密切相关的。
1.1 接口设计
对一个远程服务 Service
的调用,gRPC
约定 Client
和 Server
首先需要约定好 Service
的结构。包括一系列方法的组合,每个方法定义、参数、返回体等。对这个结构的描述,gRPC
默认是用 Protocol Buffer
去实现的。
1.2 Streaming
Streaming
在 HTTP/1.x
已经出现了,HTTP2
实现了 Streaming
的多路复用。gRPC
是基于 HTTP2
实现的。所以 gRPC
也实现了 Streaming
的多路复用,所以 gRPC
的请求有四种模式:
- Simple RPC
- Client-side Streaming RPC
- Server-side Streaming RPC
- Bidirectional Streaming RPC
也就是说同时支持单边流和双向流。
1.3 Protocol
gRPC
的协议层是基于 HTTP2
设计的,所以你如果想了解 gRPC
的话,可以先深入了解 HTTP2
。
1.4 Flow Control
gRPC
的协议支持流量控制,这里也是采用了 HTTP2
的 Flow Control
机制。
通过上面的介绍可以看到,gRPC
的高性能很大程度上依赖了 HTTP2
的能力,所以要了解 gRPC
之前,我们需要先了解一下 HTTP 2
的特性。
1.5 HTTP2 特性
- 二进制协议
众所周知,二进制协议比文本形式的协议,发送的数据量肯定是更小,传输效率更高的。所以 HTTP2
比 HTTP/1.x
更高效,因为二进制是不可读的,所以会损失部分可读性。
- 多路复用的流
HTTP/1.x
一个 Stream
是需要一个 TCP
连接的,其实从性能上来说是比较浪费的。HTTP2
可以复用 TCP
连接,实现一个 TCP
连接可以处理多个 Stream
,同时可以为每一个 Stream
设置优先级,可以用来告诉对端哪个流更重要。当资源有限的时候,服务器会根据优先级来选择应该先发送哪些流。
- 头部压缩
由于 HTTP
协议是一个无状态的协议,导致了很多相同或者类似的 HTTP
请求重复发送时,带的头部信息很多时候是完全一样的。HTTP2
对头部进行压缩,可以减少数据包大小,提高协议性能。
- 请求 Reset
在 HTTP/1.x
中,当一个含有确切值的 Content-Length
的 HTTP
消息发出之后,需要断开 TCP
连接才能中断。这样会导致需要通过三次握手来重新建立一个新的 TCP
连接,代价是比较大的。在 HTTP2
里面,我们可以通过发送 RST_STREAM
帧来终止当前消息发送,从而避免了浪费带宽和中断已有的连接。
- 服务器推送
如果一个 Client
请求资源 A
,而 Server
知道 Client
可能也会需要资源 B
, 所以在 Client
发起请求前,Server
提前将 B
推送给 A
缓存起来,从而可以缩短资源 A
这个请求的响应时间。
- Flow Control
在 HTTP2
中,每个 HTTP Stream
都有自己公示的流量窗口,对于每个 Stream
来说,Client
和 Server
都必须互相告诉对方自己能够处理的窗口大小,Stream
中的数据帧大小不得超过能处理的窗口值。
2. Protobuf 文件编写
2.1 安装 protoc 编译器
将 Proto
协议文件转换为多种语言对应格式的工具,根据对应平台选择对应的安装包,安装包下载地址https://github.com/protocolbuffers/protobuf/releases
cd ~/tmp
# 下载
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
# 解压后得到 bin 目录下的 protoc
unzip protoc-3.11.4-linux-x86_64.zip
# 创建存放 protoc 目录
sudo mkdir /usr/local/protobuf
# 复制 protoc 到刚刚创建的目录下
sudo cp bin/protoc /usr/local/protobuf/
添加 protoc 环境变量
vim /etc/profile
# 在文件末尾修改
PATH=$PATH:/usr/local/php/bin:/usr/local/protobuf
# 使其修改生效
source /etc/profile
查看是否安装成功,
protoc --versionlibprotoc 3.11.4
2.2 编写 Protocol Buffers 文件
无论使用何种语言创建客户端和服务端,都依照相同的 Proto
文件定义的协议接口和数据格式,客户端和服务器端都会使用由服务定义产生的接口代码。
user.proto
文件
syntax = "proto3"; // 指定proto版本
package user_proto; // 指定包名// 定义 User 服务
service User {// 定义 GetUser 方法 - 获取某个 user 数据rpc GetUser(GetUserRequest) returns (GetUserResponse) {}// 定义 GetUserList 方法 - 获取 user 所有数据rpc GetUserList(GetUserListRequest) returns (UserListResponse) {}
}// 枚举类型第一个字段必须为 0
enum UserSex {MEN = 0;WOMEN = 1;
}// GetUser 请求结构
message GetUserRequest {int64 userid = 1;
}// GetUser 响应结构
message GetUserResponse {int64 userid = 1;string username = 2;UserSex sex = 3;
}// GetUserList 请求结构
message GetUserListRequest {}// 响应结构
message UserListResponse {// repeated 重复(数组)repeated GetUserResponse list = 1;
}
2.3 Protobuf 文件生成于 Go 文件
要让 user.proto
生成 Go
文件,需要 protoc-gen-go
所以要下载:
go get github.com/golang/protobuf/protoc-gen-go
在 bin
目录下会生成一个 protoc-gen-go
可执行文件,就是用于生成 Go
文件的。
3. 编写 Go gRPC 服务端流程
首先创建一个为 go-grpc
的项目:
mkdir ~/go-grpc
设置 Go
模块代理,因为我们要使用 Go modules
第三方包的依赖管理工具,当然了你的 Go
环境最好是 1.13 以上。
go env -w GOPROXY=https://goproxy.io,direct
3.1 初始化这个项目
我们使用 Go modules
来初始化(创建)这个项目,毕竟是以后的主流了
cd ~/go-grpc
go mod init go-grpc
下载项目所使用的包,它们之间的依赖由 Go modules
帮我们完成了,记住一定要在项目下打开命令行下执行:
go get github.com/golang/protobuf
go get google.golang.org/grpc
创建 user_proto
目录,将刚刚编写的 user.proto
放进来:
go-grpc
├── go.mod
├── go.sum
└── user_proto└── user.proto
生成 Go
文件,这里用了 plugins
选项,提供对 gRPC
的支持,否则不会生成 Service
的接口,方便编写服务器和客户端程序:
cd ~/go-grpc/user_proto
protoc --go_out=plugins=grpc:. user.proto
根据编译指令,编译成对应语言的代码文件:
protoc -I=$SRC_DIR --xxx_out=$DST_DIR $SRC_DIR/xxx.proto
$SRC_DIR
:存放协议源文件的目录地址;$DST_DIR
:输出代码文件的目录地址;xxx.proto
:协议源文件名称;–xxx_out
:根据自己的需要,选择对应的语言,例如(Java:–java_out
,C++:–cpp_out
等);- 可通过在命令提示符中输入
protoc --help
查看更多帮助。
查看目录:
go-grpc
├── go.mod
├── go.sum
└── user_proto├── user.pb.go└── user.proto
3.2 创建服务端
让 UserServer
服务工作有两个部分:
- 实现我们服务定义的生成服务接口,做我们服务的实际工作
- 运行一个
gRPC
服务器,监听来自客户端的请求并返回服务的响应
mkdir ~/go-grpc/server
cd ~/go-grpc/server
我们首先实现 user.pb.go
中的 UserServer
,即我们服务的实际工作接口:
// UserServer is the server API for User service.
type UserServer interface {// 定义 GetUser 方法GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)// 定义 GetUserList 方法GetUserList(context.Context, *GetUserListRequest) (*UserListResponse, error)
}
创建 user.go
来实现 UserServer
接口,即我们实际的工作服务实现:
package main
import ("context""strconv"// 引入 proto 编译生成的包pb "go-grpc/user_proto"
)// 定义 User 并实现约定的接口
type User struct {UserId int64 `json:"user_id"`UserName string `json:"user_name"`
}// 获取某个 user 数据
func (this *User) GetUser(ctx context.Context, ut *pb.GetUserRequest) (*pb.GetUserResponse, error) {// 待返回数据结构resp := new(pb.GetUserResponse)resp.Userid = ut.Useridresp.Username = "laixhe"resp.Sex = pb.UserSex_MENreturn resp, nil
}// 获取 user 所有数据
func (this *User) GetUserList(ctx context.Context, ut *pb.GetUserListRequest) (*pb.UserListResponse, error) {list := make([]*pb.GetUserResponse, 0, 3)for i := 1; i <= 3; i++ {list = append(list, &pb.GetUserResponse{Userid: int64(i), Username: "laiki"+strconv.Itoa(i), Sex: pb.UserSex_MEN})}// 待返回数据结构resp := new(pb.UserListResponse)resp.List = listreturn resp, nil
}
现在我们开始编写对外服务 main.go
,以便客户端可以实际使用我们的服务:
- 创建监听
listener
- 创建
gRPC
的服务 - 将我们的服务注册到
gRPC
的Server
中 - 启动
gRPC
服务,将我们自定义的监听信息传递给gRPC
客户端
package main
import ("log""net"// 引入 proto 编译生成的包pb "go-grpc/user_proto""google.golang.org/grpc"
)func main() {// 监听地址和端口listen, err := net.Listen("tcp", ":50051")if err != nil {log.Fatalf("监听端口失败: %v", err)}// 实例化 grpc ServerserverGrpc := grpc.NewServer()// 注册 User servicepb.RegisterUserServer(serverGrpc, &User{})log.Println("开始监听 Grpc 端口 0.0.0.0:50051")// 启动服务err = serverGrpc.Serve(listen)if err != nil {log.Println("启动 Grpc 服务失败")}
}
查看目录:
go-grpc
├── go.mod
├── go.sum
├── server
│ ├── main.go
│ └── user.go
└── user_proto├── user.pb.go└── user.proto
我们回顾下:
- 首先要实现
UserServer
接口 - 创建
gRPC Server
对外端口 - 注册我们实现的
UserServer
接口的实例 - 最后调用
Serve()
启动我们的服务
4. 编写 Go gRPC 客户端流程
首先创建我们所需的目录:
mkdir ~/go-grpc/client
cd ~/go-grpc/client
4.1 初始化客户端
首先在连接我们建立好的服务端的 IP
和端口 main.go
,通过把服务器地址和端口号传递给 grpc.Dial()
来创建通道:
package main
import ("log""net/http"// 引入 proto 编译生成的包pb "go-grpc/user_proto""google.golang.org/grpc"
)const (// Address gRPC 服务地址Address = "127.0.0.1:50051"
)var UClient pb.UserClient// 初始化 Grpc 客户端
func initGrpc() {// 连接 GRPC 服务端conn, err := grpc.Dial(Address, grpc.WithInsecure())if err != nil {log.Fatalln(err)}// 初始化 User 客户端UClient = pb.NewUserClient(conn)log.Println("初始化 Grpc 客户端成功")
}// 启动 http 服务
func main() {initGrpc()http.HandleFunc("/user/get", GetUser)http.HandleFunc("/user/list", GetUserList)log.Println("开始监听 http 端口 0.0.0.0:8080")err := http.ListenAndServe(":8080", nil)if err != nil {log.Printf("http.ListenAndServe err:%v", err)}
}
对外 HTTP
的两接口的实现 user.go
:
- 创建
gRPC
连接器 - 创建
gRPC
客户端,并将连接器赋值给客户端 - 向
gRPC
服务端发起请求 - 获取
gRPC
服务端返回的结果
package main
import ("context""encoding/json""net/http""strconv"// 引入 proto 编译生成的包pb "go-grpc/user_proto"
)func GetUser(w http.ResponseWriter, r *http.Request) {// 获取 GET 的参数userid := r.FormValue("userid")id, err := strconv.ParseInt(userid, 10, 0)if err != nil {w.Write([]byte("userid The parameters must be integers"))return}// 调用 Grpc 的远程接口data, err := UClient.GetUser(context.Background(), &pb.GetUserRequest{Userid: id})if err != nil {w.Write([]byte("Grpc: " + err.Error()))return}// json 格式化js, _ := json.Marshal(data)w.Write(js)
}func GetUserList(w http.ResponseWriter, r *http.Request) {// 调用 Grpc 的远程接口data, err := UClient.GetUserList(context.Background(), &pb.GetUserListRequest{})if err != nil {w.Write([]byte("Grpc: " + err.Error()))return}// json 格式化js, _ := json.Marshal(data.List)w.Write(js)
}
我们回顾下:
- 通于
grpc.Dial()
连接服务端 - 持有某个
gRPC
的服务连接NewUserClient
RPC 笔记(03)— gRPC 概念、安装、编译、客户端和服务端示例相关推荐
- oracle11g服务器端下载,安装_oracle11G_客户端_服务端_链接_oracle
在开始之前呢,有一些注细节需要注意,oracle11G_客户端_和_服务端, 分为两种 一种是 开发者使用 一种是 BDA 自己使用(同时也需要根据自己 PC 的系统来做_win7_与 ...
- gRPC中Java和node进行异构通信-互为客户端和服务端
场景 gPRC简介以及Java中使用gPRC实现客户端与服务端通信(附代码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/ ...
- 浅议C#客户端和服务端通信的几种方法:Rest和GRPC和其他
本文来自:https://michaelscodingspot.com/rest-vs-grpc-for-asp-net/ 浅议C#客户端和服务端通信的几种方法:Rest和GRPC 在C#客户端和C# ...
- 【学习笔记】在windows下进行基于TCP的本地客户端和服务端socket通信
文章目录 socket介绍 java中使用socket 基于tcp的socket通信 使用ServerSocket类创建一个web服务器:(java) windows下的基于tcp的socket编程( ...
- java自动化测试成长日记-之CVS客户端和服务端安装和部署1:CVS服务端的配置和基本使用...
CVS服务端的配置和基本使用 在做java自动化测试集成环境搭建的时候,无论怎样,你都会选择一个源代码管理工具,如:SVN,CVS,VSS等:但如果你使用Eclipse工具,建议你使用CVS源代码管理 ...
- 项目管理---SVN,Subversion的安装,客户端和服务端
一端是保存你所有纳入版本控制的数据的Subversion版本库,在另一端是你的Subvesion客户端程序,管理着所有纳入版本控制数据的本地影射(叫做"工作拷贝"),在这两极之间是 ...
- 计算机的用户终端,计算机终端、客户端、服务端都是什么概念,他们之间的区别是什么?谢谢,大家,小弟是菜鸟...
终端也称终端设备,是计算机网络中处于网络最外围的设备.客户端或称为用户端,是指与服务器相对应,为客户提供本地服务的程序.服务端是为客户端服务的,服务的内容诸如向客户端提供资源,保存客户端数据.终端.客 ...
- Windows编译安装AzerothCore魔兽世界开源服务端Lua脚本引擎Eluna和防作弊anticheat模块教程
Windows编译安装AzerothCore魔兽世界开源服务端Lua脚本引擎Eluna和防作弊anticheat模块教程 大家好,我是艾西今天和大家聊聊魔兽世界游戏内的脚步以及防作弊模块 Eluna是 ...
- Java笔记-为客户端及服务端创建公私钥的密钥库
使用密钥库使得客户端与服务器之间进行安全的通信,通过下面的方式生成公钥私钥库: 1. 创建client及server的keystore. 2. 从keystore中导出certificate. 3. ...
最新文章
- reactor官方文档译文(2)Reactor-core模块
- css 实现table 隔行变色
- java epoll select_最新阿里、拼多多、快手Java岗面试题269 道送答案
- 使用HTML5实现刮刮卡效果
- StudentManager-java+mysql学生管理系统
- 推荐周立功先生的一本书
- docker harbor 域名_docker registry harbor
- 1.2 Filters
- mysql etimedout_Node.js获取请求ETIMEDOUT和ESOCKETTIMEDOUT
- bootstrap学习笔记-(1-初识bootstrap)
- 注入漏洞之sql注入漏洞
- NorthWind 数据库整体关系
- 不怕崩溃 Ghost令机房管理化繁为简
- SSM服装管理系统毕业设计源码080948
- 现代的linux和windows7,Windows 7 Vs. Linux谁更强
- vscode安装open in browser报错
- Android如何制作.9图片
- 将自定义的类的对像保存在本地
- USB-IF介绍及标准下载
- mac 安装 qt5 for tsmuxer
热门文章
- SpringBoot (一) :入门篇 Hello World
- 机器都会学习了,你的神经网络还跑不动?来看看这些建议
- torch.nn.Embedding理解
- https://blog.csdn.net/blmoistawinde/article/details/84329103
- LeetCode简单题之截断句子
- 通过 DLPack 构建跨框架深度学习编译器
- TensorFlow单层感知机实现
- MinkowskiNonlinearities非线性
- nvGRAPH API参考分析(二)
- 编译器架构Compiler Architecture(上)