grpc使用教程
项目介绍
分布式计算中,远程过程调用(英语:Remote Procedure Call,RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC 是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。RPC 是一种进程间通信的模式,程序分布在不同的地址空间里。如果在同一主机里,RPC 可以通过不同的虚拟地址空间(即便使用相同的物理地址)进行通讯,而在不同的主机间,则通过不同的物理地址进行交互。许多技术(通常是不兼容)都是基于这种概念而实现的。
gRPC 是 Google 发起的一个开源远程过程调用(Remote procedure call)系统。该系统基于HTTP/2协议传输,使用Protocol Buffers 作为接口描述语言。
该项目使用proto compiler, ts-protoc-gen
& @grpc/grpc-js
来构造一个在 Node.js 上运行的 gRPC 服务端程序。该程序通过proto compiler and ts-protoc-gen
插件来从.proto
文件中生成 JavaScript & TypeScript 语言的数据流接口文件,并且可以组合protoc
工具来自动生成 go 语言数据流接口文件,可以用来实现多个语言版本的服务端和客户端。
项目结构
package.json
- node.js 依赖文件以及运行设置compile-proto.sh
- 根据.proto
文件来自动生成 JS & TS 代码的脚本./proto/
-.proto
文件位置,定义了 message 和 service 格式./src/proto/
- 根据.proto
自动生成的 TypeScript 代码./dist/proto/
- 根据.proto
自动生成的 JavaScript 代码./src/server.ts
- gRPC server 的 TypeScript 文件,用来实现.proto
文件中 server 的方法和运行,以及访问地址和端口号等./src/client.ts
- gRPC client 的 TypeScript 文件,客户端的 TS 实现,但在本项目中是用 Go 语言进行客户端实现
如何搭建 TypeScript 语言下的 gRPC 服务器端?
安装依赖
在开始前,首先要求电脑安装了Node.js
,并且npm
工具能够正常使用。
安装相关依赖:
npm install
# 如果是苹果 M1用户需要安装x64工具
npm install --target_arch=x64
实现.proto
文件
编写.proto
文件并放入./proto/
目录下,推荐一个 service 编写一个.proto
文件,这里以resolver.proto
文件为例,该文件定义了一个Resolver
服务,用于将合约地址转换为 IPv4 地址:
syntax = "proto3";
package grpc;
//生成go语言文件的包位置
option go_package = "/grpc";
message IPv4Address {
string address = 1;
}
message ContractAddress {
string address = 1;
}
service Resolver {
rpc resolve(ContractAddress) returns (IPv4Address) {}
}
运行代码生成脚本
运行./compile-proto.sh
脚本来生成 TS & JS 代码(确保你的电脑具备运行 sh 脚本的环境!),该脚本使用proto compiler和ts-protoc-gen
来生成代码。默认下该脚本会读取./proto
目录下的所有.proto
文件并且把 TS 代码输出到./src/proto
目录下,JS 代码输出到./dist/proto
目录下。
sh ./compile-proto.sh
运行完脚本你可以在./src/proto
下看到.d.ts
和.ts
文件,通过这两个文件我们就可以使用 TypeScript 轻松实现 grpc server 和 grpc client。
实现 server 的 TypeScript 代码
编写server.ts
(可以是任意文件名)文件来实现 grpc server,并放入到./src
目录下。这个文件依赖于脚本生成的.d.ts
和.ts
文件,我们需要实现.proto
文件里定义的服务和方法,这里给出一个示例:
import * as grpc from "@grpc/grpc-js";
import { ResolverService, IResolverServer } from "./proto/resolver_grpc_pb";
import { ContractAddress, IPv4Address } from "./proto/resolver_pb";
//rpc访问地址和端口号
const host = "0.0.0.0:9090";
//定义server
const exampleServer: IResolverServer = {
//实现resolve方法
resolve(
call: grpc.ServerUnaryCall<ContractAddress, IPv4Address>,
callback: grpc.sendUnaryData<IPv4Address>
) {
if (call.request) {
console.log(`(server) Got client message: ${call.request.getAddress()}`);
}
//TODO 调TS包来把合约地址解析成IPv4地址
const address = new IPv4Address();
address.setAddress("1.1.1.1");
callback(null, address);
},
};
function getServer(): grpc.Server {
const server = new grpc.Server();
server.addService(ResolverService, exampleServer);
return server;
}
//运行server
if (require.main === module) {
const server = getServer();
server.bindAsync(
host,
grpc.ServerCredentials.createInsecure(),
(err: Error | null, port: number) => {
if (err) {
console.error(`Server error: ${err.message}`);
} else {
console.log(`Server bound on port: ${port}`);
server.start();
}
}
);
}
编译运行 gRPC 服务器
首先在项目目录下编译整个项目:
tsc -p .
这个命令会在./dist
目录下生成相对应的 JavaScript 文件。
运行 server:
npm run start:server
如何构建 GO 语言下的 gRPC 客户端?
在开始前,首先要求电脑安装了 Go 语言的运行环境,可以参照这个教程来进行安装。
安装protoc
工具
我们需要使用protoc
工具来生成 go 代码,因此需要根据教程安装相应的protoc
编译器,或者直接在github中进行下载,并把/bin
目录加入到系统环境变量中。
安装protoc
工具对 Go 语言的支持依赖:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
为了让protoc
编译器自动找到这些依赖,需要更新PATH
:
export PATH="$PATH:$(go env GOPATH)/bin"
使用protoc
生成代码
使用 Go 语言生成客户端代码同样需要依赖于.proto
文件所生成的代码文件,接下来在命令终端运行以下命令来生成代码(假设在./generated
目录下生成):
protoc --go_out=./generated --go-grpc_out=./generated --proto_path=./proto/*.proto
--go_out
和--go_grpc_out
分别指向生成.pb.go
和_grpc.pb.go
的文件目录,而--proto_path
则需要设置为.proto
文件的路径。注意生成的 go 文件打包名可以在.proto
文件里的option go_package = "/xx";
中设置,在示例中设置为了grpc
包。
实现 client 的 Go 代码
有了生成的文件,我们就可以在 Go 项目下实现客户端来与 gRPC 服务端进行交互(建议把上一步的代码生成到 Go 项目中方便调用)。
通过上一步生成的.pb.go
和_grpc.pb.go
文件,我们可以编写自己的client.go
来与服务端进行交互,一个简单的示例如下所示:
package mdns
import (
"context"
"google.golang.org/grpc"
"log"
pb "mdns-go/generated/grpc" // 替换为生成的Go代码的包路径
)
func getIPv4ViaGRpc(contractAddress string, grpcAddress string) (string, error) {
// 创建与 gRPC 服务器的连接
conn, err := grpc.Dial(grpcAddress, grpc.WithInsecure())
if err != nil {
log.Fatalf("连接失败:%v", err)
}
defer conn.Close()
// 创建 gRPC 客户端
client := pb.NewResolverClient(conn)
// 构造请求消息
request := &pb.ContractAddress{
Address: contractAddress,
}
// 调用 gRPC 方法
response, err := client.Resolve(context.Background(), request)
if err != nil {
log.Fatalf("调用 gRPC 方法失败:%v", err)
}
return response.GetAddress(), nil
}
我在mdns
项目下调用生成的 go 代码实现了一个getIPv4ViaGRpc()
方法,这个方法通过grpcAddress
连接 gRPC 服务端并且调用Resolve()
方法把合约地址转化为一个 IPv4 地址。