在这篇文章中,我们将一步步通过如何在 Node.js 中实现一个简单的 gRPC 服务。我们将构建一个 "Hello World" 示例,涵盖环境准备、定义服务接口、生成 gRPC 代码、实现服务、编写客户端调用服务以及运行和测试。
眼熟吧,GO:Hello gRPC
什么是 RPC 和 gRPC
RPC(Remote Procedure Call)
RPC(远程过程调用)是一种通过网络从远程计算机程序上执行过程(子程序或函数)的协议。RPC 使得程序可以像调用本地函数一样调用远程函数,隐藏了网络通信的细节。
gRPC
gRPC 是 Google 开发的一个高性能、通用的开源 RPC 框架。它基于 HTTP/2 标准,支持多种编程语言,并使用 Protocol Buffers 作为接口定义语言。gRPC 提供了负载均衡、跟踪、健康检查、认证等功能,非常适合微服务架构。
官方语言支持
这些是 gRPC 官方支持的编程语言、平台和操作系统版本:
| 语言 | 操作系统 | 编译器 / SDK |
|---|---|---|
| C/C++ | Linux, Mac | GCC 7.3.1+, Clang 6+ |
| C/C++ | Windows 10+ | Visual Studio 2019+ |
| C# | Linux, Mac | .NET Core, Mono 4+ |
| C# | Windows 10+ | .NET Core, .NET 4.5+ |
| Dart | Windows, Linux, Mac | Dart 2.12+ |
| Go | Windows, Linux, Mac | Go 1.13+ |
| Java | Windows, Linux, Mac | Java 8+ (KitKat+ for Android) |
| Kotlin | Windows, Linux, Mac | Kotlin 1.3+ |
| Node.js | Windows, Linux, Mac | Node v8+ |
| Objective-C | macOS 10.10+, iOS 9.0+ | Xcode 12+ |
| PHP | Linux, Mac | PHP 7.0+ |
| Python | Windows, Linux, Mac | Python 3.7+ |
| Ruby | Windows, Linux, Mac | Ruby 2.3+ |
- 语言:列出支持的编程语言。
- 操作系统:列出支持的操作系统版本。
- 编译器 / SDK:列出相应语言需要的编译器或 SDK 版本。
新建项目目录
创建一个新目录作为项目的根目录,例如 hello-grpc,并在其中创建一个 package.json 文件来初始化项目:
mkdir hello-grpc
cd hello-grpc
npm init -y
环境准备
确保你已经安装了 Node.js 和 npm。然后安装 @grpc/grpc-js 和 @grpc/proto-loader 包:
npm install @grpc/grpc-js @grpc/proto-loader
定义服务接口
在项目根目录中,创建一个名为 helloworld.proto 的文件,定义你的服务接口:
syntax = "proto3";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
实现服务
在项目根目录下创建一个 server.js 文件,并实现 gRPC 服务:
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// 加载 protobuf 文件
const PROTO_PATH = path.join(__dirname, 'helloworld.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true, // 保持字段名大小写
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
// 实现 SayHello RPC 方法
function sayHello(call, callback) {
// 返回带有问候消息的响应
callback(null, {message: 'Hello ' + call.request.name});
}
// 启动 gRPC 服务器
function main() {
const server = new grpc.Server();
// 添加 Greeter 服务到服务器
server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
// 绑定服务器到指定端口
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log('Server running at http://0.0.0.0:50051');
});
}
main();
代码解释
-
加载并解析 Proto 文件:
const PROTO_PATH = path.join(__dirname, 'helloworld.proto'); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;这段代码加载并解析
helloworld.proto文件,生成 gRPC 服务描述对象。 -
实现 gRPC 服务方法:
function sayHello(call, callback) { callback(null, {message: 'Hello ' + call.request.name}); }这里定义了
sayHello方法,它接受一个请求对象call和一个回调函数callback。该方法从请求中获取name字段,并返回一个带有问候消息的响应。 -
启动 gRPC 服务器:
function main() { const server = new grpc.Server(); server.addService(hello_proto.Greeter.service, {sayHello: sayHello}); server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { server.start(); console.log('Server running at http://0.0.0.0:50051'); }); } main();这段代码创建了一个 gRPC 服务器实例,将
Greeter服务添加到服务器,并绑定到0.0.0.0:50051端口。服务器启动后,会输出一条日志消息。
编写客户端调用服务
在项目根目录下创建一个 client.js 文件,并实现 gRPC 客户端:
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// 加载 protobuf 文件
const PROTO_PATH = path.join(__dirname, 'helloworld.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true, // 保持字段名大小写
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
// 创建 gRPC 客户端
function main() {
const client = new hello_proto.Greeter('localhost:50051', grpc.credentials.createInsecure());
const user = 'world'; // 默认用户名称
// 调用 SayHello 方法
client.sayHello({name: user}, (err, response) => {
if (err) {
console.error(err);
} else {
console.log('Greeting:', response.message);
}
});
}
main();
代码解释
-
加载并解析 Proto 文件:
const PROTO_PATH = path.join(__dirname, 'helloworld.proto'); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;这段代码加载并解析
helloworld.proto文件,生成 gRPC 服务描述对象。 -
创建客户端并调用服务:
function main() { const client = new hello_proto.Greeter('localhost:50051', grpc.credentials.createInsecure()); const user = 'world'; client.sayHello({name: user}, (err, response) => { if (err) { console.error(err); } else { console.log('Greeting:', response.message); } }); } main();这段代码创建了一个连接到
localhost:50051的Greeter客户端,并调用sayHello方法。调用成功后,客户端会输出服务器返回的问候消息。
交互逻辑
-
服务端启动
- 加载并解析 Proto 文件:服务端需要加载并解析定义 gRPC 服务的 Proto 文件。
- 实现 gRPC 服务方法:实现 Proto 文件中定义的 gRPC 服务方法。
- 启动 gRPC 服务器:启动 gRPC 服务器,使其开始监听客户端请求。
-
客户端调用
- 加载并解析 Proto 文件:客户端也需要加载并解析同样的 Proto 文件。
- 创建 gRPC 客户端:创建一个 gRPC 客户端实例,连接到服务端。
- 调用 gRPC 方法:客户端调用服务端提供的 gRPC 方法。
-
服务端处理请求
- 接收客户端请求:服务端接收到客户端的 gRPC 请求。
- 执行服务方法逻辑:服务端执行对应的服务方法逻辑。
- 返回响应给客户端:服务端将处理结果返回给客户端。
-
客户端接收响应
- 接收服务端响应:客户端接收到服务端的响应。
- 处理响应数据:客户端处理接收到的响应数据。
运行和测试
首先,运行服务器:
node server.js
然后,在另一个终端窗口,运行客户端:
node client.js
你应该会在客户端看到服务器的问候:
Greeting: Hello world
这就是在 Node.js 中使用 gRPC 实现 Hello World 示例的完整过程。希望这个示例对你有帮助!
通过这个示例,你应该已经掌握了在 Node.js 中使用 gRPC 的基本方法。你可以基于这个示例构建更复杂的 gRPC 服务,满足实际应用的需求。接下来的日子,我们将使用Fastify+gRPC封装基础项目,让我们下期再会~