1,582
社区成员




RPC 全称 Remote Procedure Call —— 远程过程调用。RPC 技术能够使得远程调用像本地调用一样简单方便。为了更好的理解 RPC,举个例子:某个 程序 S 实现了一个全局资源计数器 add
函数,每次资源被使用则调用该函数使计数器加 1,如:
function add (num) {
return num++;
}
const num = add(1); // num = 2
如果全局资源被 程序 S 自己使用,那么仅需要在本程序中通过 require
引用 add
函数所在的模块并直接调用 add
函数即可。但如果该资源被另一个 程序 X 使用,程序 X 占用了该全局资源并试图改变资源计数器的值,就需要通过某种间接机制调用到 add
函数。我们可能会在 程序S 暴露出一个 Restful 接口并内部调用 add
方法将计算结果返回接口调用者。这种形式固然可行,但可能引入不必要的技术细节,并且 HTTP 请求处理逻辑也相对繁琐。那么 RPC 就刚好解决了这个问题,使调用者不需要关注具体请求过程,参数传递解析过程等,只需要像调用本地方法一样,就可以实现对远程方法的调用。
此外,如下文介绍,JSRE RPC 模块还提供了双工数据流 RPC 调用。与 HTTP 请求相比,实时性更好,也无须考虑 Body 大小限制、请求认证等问题,用起来简直不要太方便!
JSRE 中的 RPC
JSRE 提供了内置的 RPC 模块是非常强大的,主要用于应用程序和系统服务进程间的通信。该模块实现了两种远程过程调用服务:
使用 AF_UNIX
类型套接字的多进程调用服务
使用 UDP
协议的网络远程调用服务
并且支持以下功能:
请求和响应。
订阅和发布。
双工数据流(仅适用于 AF_UNIX
类型)。
用户可以使用以下代码导入 Rpc
模块。
// Rpc 模块
const Rpc = require('rpc');
Rpc.Server
为 RPC 服务类,该方法支持创建两种类型服务:
1.使用 AF_UNIX
多进程通信创建 RPC 服务器。
new Rpc.Server(name[, errCallback[, onlyPrivilege]])
name
{String} RPC 服务名称。由字母、数字和下划线组成,不超过 64 个字节。
errCallback
{Function} 错误回调。默认值:undefined。
onlyPrivilege
{Boolean}只有特权任务才能访问此 RPC 服务。默认值:false。
返回:{Object} RPC 服务器对象。
const Rpc = require('rpc');
const server = new Rpc.Server('LocalServer_1');
2.使用 UDP 协议创建 RPC 服务器
new Rpc.Server(saddr[, errCallback[, onlyPrivilege]])
saddr
{Object} RPC 服务地址。
errCallback
{Function} RPC 服务 I/O 错误回调。默认值:undefined。
onlyPrivilege
{Boolean} 只有特权任务才能访问此 RPC 服务。默认值:false。
返回:{Object} RPC 服务器对象。
const Rpc = require('rpc');
const socket = require('socket');
const saddr = socket.sockaddr('127.0.0.1', 3000);
const server = new Rpc.Server(saddr);
Rpc 服务器实例
使用 new Rpc.Server() 创建的即为 RPC 服务器实例
server.on(event, callback)
RPC 服务器对象继承自EventEmitter
,可使用on()
监听事件,客户端可以通过下文的client.call
等方法以该事件名称发起远程调用并触发此 server 事件。
const Rpc = require('rpc');
const server = new Rpc.Server('LocalServer_1');
server.on('command_echo', (msg, from, seq) => {
console.log('receive command_echo', msg, from);
server.reply({ res: 'ok', msg }, from, seq);
});
其中:
msg
{Object} 客户端发送的消息或者可以理解为调用参数。
from
{Object} 客户端信息。
seq
{Integer} 自增的客户端调用序列号。
server.reply(msg, to, seq[, timeout])
向客户端发送回应
msg
{Object} 回复消息对象。
to
{Object} 回复客户端目标,直接使用 server.on 中的 from 对象即可。
seq
{Integer} 客户端命令序列号,必须与客户端请求消息序列号相同。
timeout
{Integer} 以毫秒为单位的等待超时。默认值:undefined,意味着永远等待。
返回:{Boolean}是否回复消息发送成功。
server.reverse(event, msg, to[, timeout])
触发客户端的事件,向指定的客户端发送消息
event
{String} 要触发的客户端事件。
msg
{Object} 反向消息对象。
t
{Object} 反向目标,与 reply
类似,直接使用 from
对象即可。
timeout
{Integer}
以毫秒为单位的等待超时。默认值:未定义意味着永远等待。
返回:{Boolean}是否回复消息发送成功。
const Rpc = require('rpc');
const server = new Rpc.Server('LocalServer_1');
server.on('command_echo', (msg, from, seq) => {
console.log('receive command_echo', msg, from);
server.reverse('command_client', { res: 'ok', msg }, from);
});
server.createReadStream(clientPid[, alive])
创建一个临时ReadStream
对象来接收客户端发送的流式数据,一般对应 client.createWriteStream(rid[, async]) 使用。alive
是等待客户端 WriteStream
连接的时间。如果客户端在此时间间隔内未连接,ReadStream
则会生成 'Peer timeout!'
错误。最小时间为:1000
。
clientPid
{Integer} 指定客户端进程 ID,可通过 from.pid 获取。
alive
{Integer} 最大空闲时间(毫秒)。默认值:30000。
返回:{ReadStream} Readable
流对象。
const Rpc = require('rpc');
const server = new Rpc.Server('LocalServer_1');
server.on('to_server', (msg, from, seq) => {
const r = server.createReadStream(from.pid);
const f = fs.createWriteStream('./save.txt');
r.on('error', function(error) {
console.error('Error:', error.message);
f.destroy(error);
});
f.on('finish', function() {
console.log('File save ok!');
});
r.pipe(f);
server.reply({ res: 'ok', id: r.id }, from, seq);
});
server.createWriteStream(rid[, async])
创建一个临时 WriteStream
对象以将流式数据发送到客户端,一般对应 client.createReadStream([alive]) 使用。
rid
{String} 对应的客户端传递的ReadStream
ID。
async
{Boolean} 是否使用异步模式,效率较低,但对其他事件公平。默认值:true。
返回:{WriteStream} Writable
流对象。
const Rpc = require('rpc');
const server = new Rpc.Server('LocalServer_1');
server.on('to_client', (msg, from, seq) => {
const s = server.createWriteStream(msg.id);
const f = fs.createReadStream('./save.txt');
s.on('error', function(error) {
console.error('Error:', error.message);
f.destroy(error);
});
s.on('finish', function() {
console.log('File send ok!');
});
f.on('error', function(error) {
console.error('Error:', error.message);
s.destroy(error);
})
f.pipe(s);
server.reply({ res: 'ok' }, from, seq);
});
server.close()
关闭 RPC 服务。RPC 服务在关闭后不再允许使用。
const Rpc = require('rpc');
const server = new Rpc.Server('LocalServer_1');
// 关闭服务
server.close()
Rpc.Client
为 RPC 客户端类,该方法支持创建两种类型客户端:
使用AF_UNIX
多进程通信创建 RPC 客户端
new Rpc.Client(name[, errCallback])
name
{String} RPC 服务名称。由字母、数字和下划线组成,不超过 64 个字节。
errCallback
{Function} LPC 客户端 I/O 错误回调。默认值:undefined。
返回:{Object} RPC 客户端对象。
const Rpc = require('rpc');
const client = new Rpc.Client('LocalServer_1');
2.使用 UDP
协议创建 RPC 客户端
new Rpc.Client(saddr[, errCallback])
saddr
{Object} RPC 服务器套接字地址。
errCallback
{Function} LPC 客户端 I/O 错误回调。默认值:undefined。
返回:{Object} RPC 客户端对象。
const Rpc = require('rpc');
const socket = require('socket');
const saddr = socket.sockaddr('127.0.0.1', 3000);
const client = new Rpc.Client(saddr);
使用 new Rpc.Server() 创建的即为 RPC 服务
client.call(event, msg[, callback[, timeout])
触发服务端监听的事件,向服务器发送调用请求
event
{String} 要触发的服务器事件。
msg
{Object}发送的消息。
callback
{Function} 服务器回复回调函数。
reply
{Object} 服务器回复消息。undefined
表示在指定的超时时间内未收到服务器响应。
timeout
{Integer} 以毫秒为单位的等待超时。默认值:60000。
返回:{Boolean} 是否回复消息发送成功。
const Rpc = require('rpc');
const client = new Rpc.Client('LocalServer_1');
client.call('command_echo', { foo: 'foo' }, (reply) => {
console.log('Server reply:', reply);
});
const Rpc = require('rpc');
const client = new Rpc.Client('LocalServer_1');
const reply = client.callSync('command_echo', { foo: 'foo' });
if (reply) {
console.log('Server reply:', reply.msg);
}
client.fetch(event, msg[, timeout])
向服务器发送一个调用请求,这个函数是异步请求并返回一个Promise
对象。
event
{String} 要触发的服务器事件。
msg
{Object}发送的消息。
timeout
{Integer} 以毫秒为单位的等待超时。默认值:60000。
返回:{Promise} Promise 对象。
注意:此方法对服务器响应规则具有以下约定:
服务器返回对象中的 res 字段为 'ok',表示调用成功。
当调用失败时,info 字段会被返回,并且该字段必须是字符串,说明失败的原因。
const Rpc = require('rpc');
const client = new Rpc.Client('LocalServer_1');
async function echo() {
const reply = await client.fetch('command_echo', { foo: 'bar' });
console.log(reply.msg);
}
client.createReadStream([alive])
创建一个临时WriteStream
对象以将数据发送到服务器。通常对应server.createReadStream(clientPid[, alive]) 使用。
rid
{String} 对等ReadStream
ID。
async
{Boolean} 是否使用异步模式,效率较低,但对其他事件公平。默认值:true。
返回:{WriteStream} Writable
流对象。
const Rpc = require('rpc');
const client = new Rpc.Client('LocalServer_1');
const r = client.createReadStream();
client.call('to_client', { id: r.id }, function(reply) {
if (reply && reply.res === 'ok') {
const f = fs.createWriteStream('./xxx.txt');
r.on('error', function(error) {
console.error('Error:', error.message);
f.destroy(error);
});
f.on('finish', function() {
console.log('File save ok!');
});
r.pipe(f);
}
});
client.createWriteStream(rid[, async])
创建一个临时WriteStream
对象以将数据发送到服务器。
rid
{String} 对应服务端返回的ReadStream
ID。
async
{Boolean} 是否使用异步模式,效率较低,但对其他事件公平。默认值:true。
返回:{WriteStream} Writable
流对象。
const Rpc = require('rpc');
const client = new Rpc.Client('LocalServer_1');
client.call('to_server', {}, function(reply) {
if (reply && reply.res === 'ok') {
const s = client.createWriteStream(reply.id);
const f = fs.createReadStream('./xxx.txt');
s.on('error', function(error) {
f.destroy(error);
});
s.on('finish', function() {
console.log('File send ok!');
});
f.pipe(s);
}
});
client.close()
关闭 RPC 客户端, RPC 客户端对象关闭后不再允许使用
const Rpc = require('rpc');
const client = new Rpc.Client('LocalServer_1');
// 关闭客户端
client.close();
RPC 单条消息的最大长度,通常为 16 KB。
const Rpc = require('rpc');
Rpc.MAX_MSG_SIZE = 16;
有了 RPC 就可以使我们实际场景中的一些开发变得简单,比如:我们有多个应用要根据不用的场景同时控制一台智能灯,我们只需要启动一个专门控制智能灯的 RPC 服务,然后各个业务服务可以分别创建 RPC 客户端,去调用 RPC 服务,非常方便实现复用。