node.js中RPC(远程过程调用)的实现原理介绍

刚接触到RPC(远程过程调用),就是可以在本地调用远程机子上的程序的方法,看到一个简单的nodejs实现,用来学习RPC的原理很不错:nodejs light_rpc

使用示例:

代码如下:

//服务端
var light_rpc = require('./index.js');
var port = 5556;
var rpc = new light_rpc({
    combine: function(a, b, callback){
        callback(a + b);
    },
    multiply: function(t, cb){
        cb(t*2);
    }
}).listen(port);

Sample client:

代码如下:

//客户端
rpc.connect(5556, 'localhost', function(remote, conn){
    remote.combine(1, 2, function(res){
        if(res != 3){
            console.log('ERROR', res);
        }
    });
});

简单说说整个过程:

1.server端启动程序,侦听端口,实现提供给client调用的函数(如上述例子的combine和multiply),保存在一个对象里。
2.client端启动程序,连接服务端,连接完成后发送describe命令,要求server返回它能提供调用的函数名。

代码如下:

connection.on('connect', function(){
  connection.write(command(descrCmd));
});

3.server端接收到describe命令,把自己可供调用的函数名包装好发送出去(“combine”, “multiply”)
4.client端接收到server发送的函数名,注册到自己的对象里,给每个函数名包装一个方法,使本地调用这些函数时实际上是向server端发送请求:

代码如下:

for(var p in cmd.data){
  remoteObj[p] = getRemoteCallFunction(p, self.callbacks, connection);
  //getRemoteCallFunction的实现见下面
}

5.client端调用server端的函数:

1) 给传入的callback函数生成一个唯一ID,称为callbackId,记录到client的一个对象里。
2) 包装好以下数据发送给server端:调用函数名,JSON序列化后的参数列表,callbackId

代码如下:

function getRemoteCallFunction(cmdName, callbacks, connection){
  return function(){
    var id = uuid.generate();
    if(typeof arguments[arguments.length-1] == 'function'){
      callbacks[id] = arguments[arguments.length-1];
    }
    var args = parseArgumentsToArray.call(this, arguments);
    var newCmd = command(cmdName, {id: id, args: args});
    connection.write(newCmd);
  }
}

6.server端接收到上述信息,解析数据,对参数列表反序列化,根据函数名和参数调用函数。

代码如下:

var args = cmd.data.args;
args.push(getSendCommandBackFunction(c, cmd.data.id));
self.wrapper[cmd.command].apply({}, args);

7.函数运行完成后,把结果序列化,连同之前收到的callbackId发送回client端

代码如下:

function getSendCommandBackFunction(connection, cmdId){
  return function(){
    var innerArgs = parseArgumentsToArray.call({}, arguments);
    var resultCommand = command(resultCmd, {id: cmdId, args: innerArgs});
    connection.write(resultCommand);
  };
}

8.client端接收到函数运行结果和callbackId,根据callbackId取出回调函数,把运行结果传入回调函数中执行。

9.整个过程完成,详见源码:https://github.com/romulka/nodejs-light_rpc

几个注意的点:

1.整个过程中client和server一直保持连接,不像http协议发送和接收完就断开链接,所以不能以断开链接判断一次数据的传送完成。为了判断数据接收完成,client和server发送的数据遵循一个简单的协议:在数据前加上数据包的长度和分隔符,如定分隔符为\n:[数据包长度\n数据],这样在收到数据后首先取出数据包的长度,再不断判断累计已接收到的数据包是否等于或超过这个长度,若是则一次数据传送完成,可以开始解析提取数据。

2.这个RPC简单在于没有考虑参数里有函数类型的情况,例如有参数是一个object,这个object下有函数成员,JSON序列化时会把函数忽略,在server端是执行不了这个函数的。

为了解决这个问题,需要进行复杂的处理:

1.深度遍历每个要发送给远端的参数,把函数成员抽出来,给这个函数生成唯一id,放到本地一个对象里,把这个函数成员替换成这个id字符串,并标识这个成员实际上是一个函数。这样这个对象就可以序列化发送出去了。
2.server接收到调用,当要使用参数object里的函数时,判断到这是一个经过client处理过的函数,有一个id,把这个id发送回client端,并用同样的方法把自身的回调函数id传给client,等待client端的回调。
3.client端接收到这个函数id,找到这个函数实体,调用,完成后根据server端给的回调id发送回给server端
4.server端收到结果,找到回调函数,继续执行,完成。

函数的记录方法可以以其他方式完成,大体思路就是把函数替换成可序列化的东西,记录函数以便remote端调用时能在本地找到这个函数。可以参考dnode的实现。

(0)

相关推荐

  • node.js中RPC(远程过程调用)的实现原理介绍

    刚接触到RPC(远程过程调用),就是可以在本地调用远程机子上的程序的方法,看到一个简单的nodejs实现,用来学习RPC的原理很不错:nodejs light_rpc 使用示例: 复制代码 代码如下: //服务端 var light_rpc = require('./index.js'); var port = 5556; var rpc = new light_rpc({     combine: function(a, b, callback){         callback(a + b

  • Node.js中的缓冲与流模块详细介绍

    缓冲(buffer)模块 js起初就是为浏览器而设计的,所以能很好的处理unicode编码的字符串,但不能很好的处理二进制数据.这是Node.js的一个问题,因为Node.js旨在网络上发送和接收经常是以二进制格式传输的数据.比如: - 通过TCP连接发送和接收数据:  - 从图像或者压缩文件读取二进制数据:  - 从文件系统读写数据:  - 处理来自网络的二进制数据流 而Buffer模块为Node.js带来了一种存储原始数据的方法,于是可以再js的上下文中使用二进制数据.每当需要在Node.j

  • Node.js中对通用模块的封装方法

    在Node.js中对模块载入和执行进行了包装,使得模块文件中的变量在一个闭包中,不会污染全局变量,和他人冲突. 前端模块通常是我们开发人员为了避免和他人冲突才把模块代码放置在一个闭包中. 如何封装Node.js和前端通用的模块,我们可以参考Underscore.js 实现,他就是一个Node.js和前端通用的功能函数模块,查看代码: 复制代码 代码如下: // Create a safe reference to the Underscore object for use below.  var

  • node.js中module.exports与exports用法上的区别

    Node.js 引入了模块(Module)概念,一个模块可以通过module.exports 或 exports 将函数.变量等导出,以使其它 JavaScript 脚本通过require() 函数引入并使用. module.exports 初始值为一个空对象 {},所以 exports 初始值也是 {},exports 是指向的 module.exports 的引用,在模块内部大概是这样: exports = module.exports = {}; 举个栗子,在node.js中创建模块非常简

  • Node.js 中exports 和 module.exports 的区别

    Node.js 中exports 和 module.exports 的区别 require 用来加载代码,而 exports 和 module.exports 则用来导出代码.但很多新手可能会迷惑于 exports 和 module.exports 的区别,为了更好的理解 exports 和 module.exports 的关系,我们先来巩固下 js 的基础.示例: test.js var a = {name: 1}; var b = a; console.log(a); console.log

  • 详解Node.js中的事件机制

    前言 在前端编程中,事件的应用十分广泛,DOM上的各种事件.在Ajax大规模应用之后,异步请求更得到广泛的认同,而Ajax亦是基于事件机制的. 通常js给我们的第一印象就是运行在客户端浏览器上面的脚本,通过node.js我们可以在服务端运行javascript. node.js是基于单线程无阻塞异步式的I/O,异步式的I/O指的是当遇到I/O操作的时候,线程不阻塞而是进行下面的操作,那么I/O操作完成之后,线程时如何知道该操作完成的呢? 当操作完成耗时的I/O操作之后,会以事件的形式通知I/O操

  • node.js中http模块和url模块的简单介绍

    前言 本文主要给大家介绍了关于node.js中http模块与url模块的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 一.http模块的简单介绍 node.js当中的http内置模块可以用于创建http服务器与http客户端. 1.引包 const http = require('http'); 2.创建http服务器 var server = http.createServer((req,res)=>{ }); 使用http的.createServer()方法可以

  • node.js中grunt和gulp的区别详解

    node.js中grunt和gulp的区别详解 自nodeJS登上前端舞台,自动化构建变得越来越流行.目前最流行的当属grunt和gulp,这两个光看名字挺像,功能也差不多,不过gulp能在grunt这位大哥如日中天的境况下开辟出自己的一片天地,有着她独到的优点. 易用 Gulp相比Grunt更简洁,而且遵循代码优于配置策略,维护Gulp更像是写代码. 高效 Gulp相比Grunt更有设计感,核心设计基于Unix流的概念,通过管道连接,不需要写中间文件. 高质量 Gulp的每个插件只完成一个功能

  • Node.js中 __dirname 的使用介绍

    前言 本文主要给大家介绍的是关于Node.js中 __dirname 使用的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 方法如下 新建个文件 app.js 里面的内容如下: console.log(__dirname + '/example.db'); console.log('example.db'); 如果将app.js放在一个根目录下面 执行node app.js 分别输出如下内容: /Users/durban/nodejs/koa-mysql-orm-model/exa

  • 完美解决node.js中使用https请求报CERT_UNTRUSTED的问题

    只要调用了没有受信的https就会报错:CERT_UNTRUSTED 简单的解决方法就是设置环境变量回避非授信证书的问题. 只要在请求的代码之前加上如下代码即可: process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 结束!!! 以上就是小编为大家带来的完美解决node.js中使用https请求报CERT_UNTRUSTED的问题全部内容了,希望大家多多支持我们~

随机推荐