Skip to main content

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 compilerts-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 地址。