爱智中的 RPC,使实际场景中的开发变得如此简单

leecactus0 2022-01-21 15:23:35

什么是 RPC?

 

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 类型)。

 

01如何使用

  • RPC服务使用流程

 

 用户可以使用以下代码导入 Rpc 模块。

// Rpc 模块
const Rpc = require('rpc');
  • Rpc.Server

     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} 对应的客户端传递的ReadStreamID。

      • 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.Client 为 RPC 客户端类,该方法支持创建两种类型客户端:

      1. 使用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);
      • Rpc 客户端实例

      使用 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} 对应服务端返回的ReadStreamID。

      • 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.MAX_MSG_SIZE

      RPC 单条消息的最大长度,通常为 16 KB。

      
      const Rpc = require('rpc');
      Rpc.MAX_MSG_SIZE = 16;

      有了 RPC 就可以使我们实际场景中的一些开发变得简单,比如:我们有多个应用要根据不用的场景同时控制一台智能灯,我们只需要启动一个专门控制智能灯的 RPC 服务,然后各个业务服务可以分别创建 RPC 客户端,去调用 RPC 服务,非常方便实现复用。



      •  
      ...全文
      871 回复 打赏 收藏 举报
      写回复
      回复
      切换为时间正序
      请发表友善的回复…
      发表回复
      相关推荐
      发帖
      爱智开发者社区

      1524

      社区成员

      爱智开发者平台是一个开放的物联网平台,通过爱智世界,应用开发者可以把自己的应用分发到亿万用户的设备上,硬件开发者能够把设备能力开放给海量的开发者,让优质的应用脱颖而出,为用户提供更优秀的使用体验。
      边缘计算物联网javascript 企业社区
      社区管理员
      • EdgerOS
      • Lumos_zbj
      • dayinfinite
      加入社区
      帖子事件
      创建了帖子
      2022-01-21 15:23
      社区公告
      暂无公告