gRPC的4种通信方式
1. gRPC简介
RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。
gRPC = google开发的RPC协议
优点:
1、接口有更严格的约束
2、更安全
3、性能更好 。
这3个优点来源于gRPC使用的protobuf(一种数据传输格式和规范)
参考相关连接
gRPC详解
Google Protobuf简明教程
2. gRPC的4种通讯方式
- 简单gRPC调用:客户端通过stub发起请求,等待服务端
callback()
返回结果,就像本地调用一样 - 服务端流式调用:客户端发起一次请求,服务端不是返回一个结果,而是将一组结果通过流
call()
返回 - 客户端流式调用:客户端发起一组请求
write()
,on()
,服务端等到客户端所有请求发送完毕,接收到客户端的end()
调用,此时服务端callback()
发送一次结果给客户端 - 客户端服务端双向流:客户端和服务端双向流互不干预,可各种按照自己的顺序消费处理,比如服务端可以选择每次接受客户端一个请求就
write()
,on()
返回一个结果,也可以选择等客户端所有请求发送完毕收到客户端的end()
调用再把所有的返回结果一次性call()
返回给客户端
3. gRPC在node.js中实现
3.0 node.js环境配置
需要安装npm i @grpc/grpc-js
, @grpc/proto-loader
, google-protobuf
, grpc-tools
, async
或者以下package.json文件直接安装npm install
这里需要注意安装的是@grpc/grpc-js
而不是grpc
,如果安装的是grpc,只需将后续文件中的var grpc = require(’@grpc/grpc-js’);改为var grpc = require(‘grpc’);,同时改变server.bindAsync()为server.bind()即可。
{
"name": "grpc-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@grpc/grpc-js": "^1.2.12",
"@grpc/proto-loader": "^0.6.0",
"async": "^3.2.0",
"google-protobuf": "*",
"grpc-tools": "^1.11.1",
"lodash": "^4.17.21"
}
}
3.1 定义protobuf的文件.proto
根目录创建proto文件夹并创建helloworld.proto文件
syntax = "proto3";
// option java_package = "ex.grpc";
// option objc_class_prefix = "HSW";
package helloworld;
service Greeter {
// 简单gRPC调用
rpc sayHelloSimple (HelloRequest) returns (HelloReply) {}
// 服务端流式调用
rpc sayHelloServer (HelloRequest) returns (stream HelloReply) {}
// 客户端流式调用
rpc sayHelloClient (stream HelloRequest) returns (HelloReply) {}
// 客户端服务端双向流
rpc sayHelloDouble (stream HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
3.2 简单gRPC调用
3.2.1 服务端
var PROTO_PATH = __dirname + '/protos/helloworld.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
// 简单gRPC调用
function sayHelloSimple(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}
async function main() {
var server = new grpc.Server();
server.addService(hello_proto.Greeter.service, {sayHelloSimple: sayHelloSimple});
await new Promise((resolve, reject) => {
server.bindAsync(
`0.0.0.0:50051`,
grpc.ServerCredentials.createInsecure(),
(err, result) => (err ? reject(err) : resolve(result))
);
});
server.start();
// 显示是否启动成功
console.log(server.started);
}
main();
3.2.2 客户端
var PROTO_PATH = __dirname + '/protos/helloworld.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
function main() {
var client = new hello_proto.Greeter('0.0.0.0:50051', grpc.credentials.createInsecure());
client.sayHelloSimple({name: 'World, Eden!!!'}, function(err, response) {
console.log('Greeting:', response.message);
});
}
main();
3.2.3 启动结果
依次启动服务端和客户端,分别在terminal中出现一下内容,即简单gRPC通信完成
// 服务端
>>> node server.js
true
// 客户端
>>> node client.js
Greeting: Hello World, Eden!!!
3.3 流式gRPC调用
另外三种方式均为流式,已双向流式通信为例进行介绍,在proto中的方法为 rpc sayHelloDouble (stream HelloRequest) returns (stream HelloReply) {}
3.3.1 服务端
服务端新增客户端服务端双向流方法,同时注册多个service
var PROTO_PATH = __dirname + '/protos/helloworld.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
// 简单gRPC调用
function sayHelloSimple(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}
// 服务端流式调用
function sayHelloServer(call, callback) {
}
// 客户端流式调用
function sayHelloClient(call, callback) {
}
// 客户端服务端双向流
function sayHelloDouble(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}
async function main() {
var server = new grpc.Server();
server.addService(hello_proto.Greeter.service, {sayHelloSimple: sayHelloSimple, sayHelloServer: sayHelloServer, sayHelloClient: sayHelloClient, sayHelloDouble: sayHelloDouble});
await new Promise((resolve, reject) => {
server.bindAsync(
`0.0.0.0:50051`,
grpc.ServerCredentials.createInsecure(),
(err, result) => (err ? reject(err) : resolve(result))
);
});
server.start();
// 显示是否启动成功
console.log(server.started);
}
main();
3.3.2 客户端
var PROTO_PATH = __dirname + '/protos/helloworld.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
function main() {
var client = new hello_proto.Greeter('0.0.0.0:50051', grpc.credentials.createInsecure());
client.sayHelloSimple({ name: 'World, Eden!!!' }, function (err, response) {
console.log('Greeting:', response.message);
});
let call = client.sayHelloDouble();
call.on('data', function (response) {
console.log('客户端receive:', response);
});
call.on('end', function () {
console.log('服务器发送end,客户端关闭');
});
call.write({ name: 'Eden1' });
call.write({ name: 'Eden2' });
call.write({ name: 'Eden3' });
call.end();
}
main();
3.3.3 启动结果
双向流通信中同时包括简单gRPC通信的方法,所以同时包含两个结果
// 服务端
>>> node server.js
true
服务端receive: { name: 'Eden1' }
服务端receive: { name: 'Eden2' }
服务端receive: { name: 'Eden3' }
服务端收到end,给客户端发送end
// 客户端
>>> node client.js
Greeting: Hello World, Eden!!!
客户端receive: { message: 'Hello Eden1' }
客户端receive: { message: 'Hello Eden2' }
客户端receive: { message: 'Hello Eden3' }
服务器发送end,客户端关闭
其他gRPC实战项目
gRPC实战–如何在NodeJS中有效使用gRPC流
该链接主要包括客户端流通信,和服务端流通信,为通讯方式的第2,3种,刚好与本文的第1,4种互补,读者可自行参阅。