1. gRPC 概念

gRPCGoogle 开源的一款高性能的 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 约定 ClientServer 首先需要约定好 Service 的结构。包括一系列方法的组合,每个方法定义、参数、返回体等。对这个结构的描述,gRPC 默认是用 Protocol Buffer 去实现的。

1.2 Streaming

StreamingHTTP/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 的协议支持流量控制,这里也是采用了 HTTP2Flow Control 机制。
通过上面的介绍可以看到,gRPC 的高性能很大程度上依赖了 HTTP2 的能力,所以要了解 gRPC 之前,我们需要先了解一下 HTTP 2 的特性。

1.5 HTTP2 特性

  1. 二进制协议

众所周知,二进制协议比文本形式的协议,发送的数据量肯定是更小,传输效率更高的。所以 HTTP2HTTP/1.x 更高效,因为二进制是不可读的,所以会损失部分可读性。

  1. 多路复用的流

HTTP/1.x 一个 Stream 是需要一个 TCP 连接的,其实从性能上来说是比较浪费的。HTTP2 可以复用 TCP 连接,实现一个 TCP 连接可以处理多个 Stream,同时可以为每一个 Stream 设置优先级,可以用来告诉对端哪个流更重要。当资源有限的时候,服务器会根据优先级来选择应该先发送哪些流。

  1. 头部压缩

由于 HTTP 协议是一个无状态的协议,导致了很多相同或者类似的 HTTP 请求重复发送时,带的头部信息很多时候是完全一样的。HTTP2 对头部进行压缩,可以减少数据包大小,提高协议性能。

  1. 请求 Reset

HTTP/1.x 中,当一个含有确切值的 Content-LengthHTTP 消息发出之后,需要断开 TCP 连接才能中断。这样会导致需要通过三次握手来重新建立一个新的 TCP 连接,代价是比较大的。在 HTTP2 里面,我们可以通过发送 RST_STREAM 帧来终止当前消息发送,从而避免了浪费带宽和中断已有的连接。

  1. 服务器推送

如果一个 Client 请求资源 A,而 Server 知道 Client 可能也会需要资源 B, 所以在 Client 发起请求前,Server 提前将 B 推送给 A 缓存起来,从而可以缩短资源 A 这个请求的响应时间。

  1. Flow Control

HTTP2 中,每个 HTTP Stream 都有自己公示的流量窗口,对于每个 Stream 来说,ClientServer 都必须互相告诉对方自己能够处理的窗口大小,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 服务工作有两个部分:

  1. 实现我们服务定义的生成服务接口,做我们服务的实际工作
  2. 运行一个 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,以便客户端可以实际使用我们的服务:

  1. 创建监听 listener
  2. 创建 gRPC 的服务
  3. 将我们的服务注册到 gRPCServer
  4. 启动 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

我们回顾下:

  1. 首先要实现 UserServer 接口
  2. 创建 gRPC Server 对外端口
  3. 注册我们实现的 UserServer 接口的实例
  4. 最后调用 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

  1. 创建 gRPC 连接器
  2. 创建 gRPC 客户端,并将连接器赋值给客户端
  3. gRPC 服务端发起请求
  4. 获取 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)
}

我们回顾下:

  1. 通于 grpc.Dial() 连接服务端
  2. 持有某个 gRPC 的服务连接 NewUserClient

RPC 笔记(03)— gRPC 概念、安装、编译、客户端和服务端示例相关推荐

  1. oracle11g服务器端下载,安装_oracle11G_客户端_服务端_链接_oracle

    在开始之前呢,有一些注细节需要注意,oracle11G_客户端_和_服务端, 分为两种   一种是  开发者使用    一种是  BDA  自己使用(同时也需要根据自己 PC 的系统来做_win7_与 ...

  2. gRPC中Java和node进行异构通信-互为客户端和服务端

    场景 gPRC简介以及Java中使用gPRC实现客户端与服务端通信(附代码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/ ...

  3. 浅议C#客户端和服务端通信的几种方法:Rest和GRPC和其他

    本文来自:https://michaelscodingspot.com/rest-vs-grpc-for-asp-net/ 浅议C#客户端和服务端通信的几种方法:Rest和GRPC 在C#客户端和C# ...

  4. 【学习笔记】在windows下进行基于TCP的本地客户端和服务端socket通信

    文章目录 socket介绍 java中使用socket 基于tcp的socket通信 使用ServerSocket类创建一个web服务器:(java) windows下的基于tcp的socket编程( ...

  5. java自动化测试成长日记-之CVS客户端和服务端安装和部署1:CVS服务端的配置和基本使用...

    CVS服务端的配置和基本使用 在做java自动化测试集成环境搭建的时候,无论怎样,你都会选择一个源代码管理工具,如:SVN,CVS,VSS等:但如果你使用Eclipse工具,建议你使用CVS源代码管理 ...

  6. 项目管理---SVN,Subversion的安装,客户端和服务端

    一端是保存你所有纳入版本控制的数据的Subversion版本库,在另一端是你的Subvesion客户端程序,管理着所有纳入版本控制数据的本地影射(叫做"工作拷贝"),在这两极之间是 ...

  7. 计算机的用户终端,计算机终端、客户端、服务端都是什么概念,他们之间的区别是什么?谢谢,大家,小弟是菜鸟...

    终端也称终端设备,是计算机网络中处于网络最外围的设备.客户端或称为用户端,是指与服务器相对应,为客户提供本地服务的程序.服务端是为客户端服务的,服务的内容诸如向客户端提供资源,保存客户端数据.终端.客 ...

  8. Windows编译安装AzerothCore魔兽世界开源服务端Lua脚本引擎Eluna和防作弊anticheat模块教程

    Windows编译安装AzerothCore魔兽世界开源服务端Lua脚本引擎Eluna和防作弊anticheat模块教程 大家好,我是艾西今天和大家聊聊魔兽世界游戏内的脚步以及防作弊模块 Eluna是 ...

  9. Java笔记-为客户端及服务端创建公私钥的密钥库

    使用密钥库使得客户端与服务器之间进行安全的通信,通过下面的方式生成公钥私钥库: 1. 创建client及server的keystore. 2. 从keystore中导出certificate. 3. ...

最新文章

  1. reactor官方文档译文(2)Reactor-core模块
  2. css 实现table 隔行变色
  3. java epoll select_最新阿里、拼多多、快手Java岗面试题269 道送答案
  4. 使用HTML5实现刮刮卡效果
  5. StudentManager-java+mysql学生管理系统
  6. 推荐周立功先生的一本书
  7. docker harbor 域名_docker registry harbor
  8. 1.2 Filters
  9. mysql etimedout_Node.js获取请求ETIMEDOUT和ESOCKETTIMEDOUT
  10. bootstrap学习笔记-(1-初识bootstrap)
  11. 注入漏洞之sql注入漏洞
  12. NorthWind 数据库整体关系
  13. 不怕崩溃 Ghost令机房管理化繁为简
  14. SSM服装管理系统毕业设计源码080948
  15. 现代的linux和windows7,Windows 7 Vs. Linux谁更强
  16. vscode安装open in browser报错
  17. Android如何制作.9图片
  18. 将自定义的类的对像保存在本地
  19. USB-IF介绍及标准下载
  20. mac 安装 qt5 for tsmuxer

热门文章

  1. SpringBoot (一) :入门篇 Hello World
  2. 机器都会学习了,你的神经网络还跑不动?来看看这些建议
  3. torch.nn.Embedding理解
  4. https://blog.csdn.net/blmoistawinde/article/details/84329103
  5. LeetCode简单题之截断句子
  6. 通过 DLPack 构建跨框架深度学习编译器
  7. TensorFlow单层感知机实现
  8. MinkowskiNonlinearities非线性
  9. nvGRAPH API参考分析(二)
  10. 编译器架构Compiler Architecture(上)