客服:点击这里给我发消息

手机:15193566610 马先生

奈特网络科技,武威网站建设公司

您的位置:当前位置:武威网站-武威奈特网 > 网站建设 > 建站知识 >

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

来源:未知   发表时间:2015-11-10 10:09

       使用示例:

      代码如下:

      //服务端

      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的实现。