1. mailbox数据收发模块

一个RPC客户端可能同一时候须要调用多个远端(server)提供的服务。在pomelo里每一个server抽象为一个mailbox。先来看看mailbox的实现:

  1. var MailBox = function(server, opts) {
  2. EventEmitter.call(this);
  3. this.curId = 1;
  4. this.id = server.id;
  5. this.host = server.host;
  6. this.port = server.port;
  7. this.protocal = server.protocal || 'http:';
  8. this.requests = {};
  9. this.timeout = {};
  10. this.queue = [];
  11. this.connected = false;
  12. this.closed = false;
  13. this.opts = opts;
  14. this.timeoutValue = 1000;
  15. this.buffMsg = opts.buffMsg;
  16. this.interval= 300;
  17. };
  18. util.inherits(MailBox, EventEmitter);

配置信息比較简单,相比服务端客户端多了一个超时的处理:

  1. var id = this.curId++;
  2. this.requests[id] = cb;
  3. setCbTimeout(this, id, cb);
  4. var pkg = {id: id, msg: msg};
  5. if(this.buffMsg) {
  6. enqueue(this, pkg);
  7. }
  8. else {
  9. this.socket.emit('message', pkg);
  10. }

curId能够理解为通信过程中的序列号,每次自增,唯一标示一个数据包。通经常使用来解决数据包的乱序问题。

假设buffMsg被设置则启用缓冲队列,和服务端一致。在发送数据之前会开启一个定时器。假设超时则回调通知上层。


2. mailstation 消息路由

mailstation主要实现了几个功能:

  1. 客户端状态控制
  2. 远程服务端信息管理
  3. 过滤器
  4. 消息路由

1. 消息路由

消息路由模块採用延迟载入的方式,加给mailstation加入远程服务端配置信息的时候没有立即载入一个mailbox与之相应。而是在真正对该服务器请求服务的时候创建相应的实例:

  1. var lazyConnect = function(station, serverId, factory, cb) {
  2. console.log('lazyConnect create mailbox and try to connect to remote server');
  3. var server = station.servers[serverId];
  4. var online = station.onlines[serverId];
  5. if(!server) {
  6. console.log('unkone server: ' + serverId);
  7. return false;
  8. }
  9. if(!online || online !== 1) {
  10. console.log('server is not onlone: ' + serverId);
  11. }
  12. var mailbox = factory.create(server, station.opts);
  13. station.connecting[serverId] = true;
  14. station.mailboxes[serverId] = mailbox;
  15. station.connect(serverId, cb);
  16. return true;
  17. };

首次请求服务的时候先通过lazyConnect建立链接,并把请求加入待处理任务队列:

  1. var addToPending = function(station, serverId, args) {
  2. console.log('add pending request to pending queue');
  3. var pending = station.pendings[serverId];
  4. if(!pending) {
  5. pending = station.pendings[serverId] = [];
  6. }
  7. if(pending.length > station.pendingSize) {
  8. console.log('station pending too much for: ' + serverId);
  9. return;
  10. }
  11. pending.push(args);
  12. };

2. 过滤器

pemelo实现了beforeafter filter能够注入函数在请求发生之前以及之后做一些处理:

  1. var doFilter = function(err, serverId, msg, opts, filters, index, operate, cb) {
  2. if(index < filters.length) {
  3. console.log('doFilter ' + operate + 'filter' + filters[index].name);
  4. }
  5. if(index >= filters.length || !!err) {
  6. utils.invokeCallback(cb, err, serverId, msg, opts);
  7. return;
  8. }
  9. var self = this;
  10. var filter = filters[index];
  11. if(typeof filter === 'function') {
  12. filter(serverId, msg, opts, function(target, message, options) {
  13. index++;
  14. if(utils.getObjectClass(target) === 'Error') {
  15. doFilter(target, serverId, msg, opts, filters, index, operate, cb);
  16. }
  17. else {
  18. doFilter(null, target || serverId, message||msg, options||opts, filters, index, operate, cb);
  19. }
  20. });
  21. return;
  22. }
  23. if(typeof filter[operate] === 'function') {
  24. filter[operate](serverId, msg, opts, function(target, message, options) {
  25. index++;
  26. if(utils.getObjectClass(target) === 'Error') {
  27. doFilter(target, serverId, msg, opts, filters, index, operate, cb);
  28. }
  29. else {
  30. doFilter(null, target || serverId, message||msg, options||opts, filters, index, operate, cb);
  31. }
  32. });
  33. return;
  34. }
  35. index++;
  36. doFilter(err, serverId, msg, opts, filters, index, operate, cb);
  37. };

看起来有点乱:),採用递归的方式依次调用各个过滤器。

来看个mailstation模块的大体流程图:

3. 服务端代理模块

架在mailstation模块上面的是服务端代理模块。

该模块完毕了对服务端的抽象。使得调用远程服务变的十分优雅。

  1. Client.prototype.addProxies = function(records) {
  2. if(!records || !records.length) {
  3. return;
  4. }
  5. for(var i = 0, l = records.length; i < l; i++) {
  6. this.addProxy(records[i]);
  7. }
  8. };

上层通过addProxies接口加入远程服务器配置信息,客户端模块会自己主动为该服务生成代理:

  1. var generateProxy = function(client, record, context) {
  2. if(!record) {
  3. return;
  4. }
  5. var res, name;
  6. var modules = Loader.load(record.path, context);
  7. if(modules) {
  8. res = {};
  9. for(name in modules) {
  10. res[name] = Proxy.create({
  11. service: name,
  12. origin: modules[name],
  13. attach: record,
  14. proxyCB: proxyCB.bind(null, client)
  15. });
  16. }
  17. }
  18. return res;
  19. };

和服务器端配置相似,record注入一个文件路径。我们须要载入该文件提供的模块。

假设record.namespace为:user, 远程服务器类型为test, record.path相应的文件路径为: /remore/test/service.js该文件导出两个模块分别包括一个接口:func1func2。在模块载入完毕之后相应的路由信息大致例如以下:

  1. proxies : {
  2. user: {
  3. test: {
  4. module1: {
  5. func1-Proxy: 'xxx'
  6. },
  7. module2: {
  8. func2-Proxy: 'zzz'
  9. }
  10. }
  11. }
  12. }

终于会为每一个服务端的每一个接口生成一个代理:

  1. var genObjectProxy = function(service, origin, attach, proxyCB) {
  2. var res = {};
  3. for(var field in origin) {
  4. if(typeof origin[field] === 'function') {
  5. res[field] = genFunctionProxy(service, field, origin, attach, proxyCB);
  6. }
  7. }
  8. return res;
  9. };
  10. var genFunctionProxy = function(serviceName, methodName, origin, attach, proxyCB) {
  11. return (function() {
  12. var proxy = function() {
  13. var args = Array.prototype.slice.call(arguments);
  14. proxyCB.call(null, serviceName, methodName, args, attach);
  15. };
  16. proxy.toServer = function() {
  17. var args = Array.prototype.slice.call(arguments);
  18. proxyCB.call(null, serviceName, methodName, args, attach, true);
  19. };
  20. return proxy;
  21. })();
  22. };

能够看到我们看到全部接口的代理都是通过封装一个proxyCB函数来完毕的。来看看proxyCB的实现:

  1. var proxyCB = function(client, serviceName, methodName, args, attach, target) {
  2. if(client.state !== STATE_STARTED) {
  3. console.log('fail to invoke rpc proxy client not running');
  4. return;
  5. }
  6. if(args.length < 2) {
  7. return;
  8. }
  9. var cb = args.pop();
  10. var routrParam = args.shift();
  11. var serverType = attach.serverType;
  12. var msg = {namespace: attach.namespace, serverType: serverType,
  13. service: serviceName, method: methodName, args: args};
  14. if(target) {
  15. target(client, msg, serverType, routrParam, cb);
  16. }
  17. else {
  18. getRouteTarget(client, serverType, msg, routrParam, function(err, serverId) {
  19. if(!!err) {
  20. utils.invokeCallback(cb, err);
  21. }
  22. else {
  23. client.rpcInvoke(serverId, msg, cb);
  24. }
  25. });
  26. }
  27. };

serviceName表示模块名称,method相应模块下的接口名称, args是调用接口传入的參数数组。attach表示原始的record路径信息。

这里有个getRouteTarget接口,我们知道当远程有多个提供相似服务的服务器为了均衡负载,须要把请求尽量平均的分配到各个服务器。


这样RPC模块基本了解完了,想要了解很多其它到这里下载代码

pomelo研究笔记-RPCclient的更多相关文章

  1. pomelo研究笔记-RPC服务端

    POMELO 採用多进程的架构能够非常好的实现游戏server(进程)的扩展性,达到支撑较多在线用户.减少server压力等要求. 进程间通信採用RPC的形式来完毕,pomelo的RPC实现的相当静止 ...

  2. 安装Pomelo 时遇到的坑

    一.Pomelo相关的代码地址 https://github.com/NetEase,这里面包含比较多的项目. 2. https://github.com/NetEase/pomelo/wiki/%E ...

  3. 用Pomelo 搭建一个简易的推送平台

    前言 实际上,个人感觉,pomelo 目前提供的两个默认sioconnector和hybridconnector 使用的协议并不适合用于做手机推送平台,在pomelo的一份公开ppt里面,有提到过, ...

  4. Pomelo:网易开源基于 Node.js 的游戏服务端框架

    Pomelo:网易开源基于 Node.js 的游戏服务端框架 https://github.com/NetEase/pomelo/wiki/Home-in-Chinese

  5. Skynet Pomelo Erlang Elixir 的认识

    1.skynet pomelo(node.js) elixir(erlang) 周末研究总结 手游这两年发展来看,感觉对实时性要求越来越高,有同事在研究Elixir开发,google得知这东西是基于e ...

  6. pomelo架构概览

    pomelo之所以简单易用.功能全面,并且具有高可扩展性.可伸缩性等特点,这与它的技术选型和方案设计是密不可分的.在研究大量游戏引擎设计思路基础上,结合以往游戏开发的经验,确定了pomelo框架的设计 ...

  7. 读pomelo的教程-2

    下面从头到尾记录chat demo的Login的过程 client:点击login按钮,取得username和rid两个值 $("#login").click(function() ...

  8. Windows安装pomelo过程

    安装总要出点状况的.操作系统是win7 64bit. 为了保证顺利,打开的是VS2012命令行提示.运行 npm install -g pomelo 经过一系列输出,最后安装提示完成了.但是输入 po ...

  9. 读pomelo的教程-1

    pomelo教程的例子是一个聊天室,包括一个webserver客户端,和一个gameserver的pomelo服务器.这个例子挺好,一个聊天系统逻辑简单,还包括了用户管理,客户端request,服务器 ...

随机推荐

  1. CSS 初始化 代码

    腾讯QQ官网 样式初始化 ;} body{font:12px"宋体","Arial Narrow",HELVETICA;background:#fff;-web ...

  2. CentOS6.5安装elasticsearch+logstash+kibana

    首先卸载低版本的java环境,然后安装 java环境和Apache服务 yum install -y java--openjdk httpd 安装ES环境 elasticsearch wget htt ...

  3. 编程的毛病——C++之父访谈

    原文见:http://www.technologyreview.com/InfoTech/17831/  翻译:xeon 11/29/2006 在20世纪的80年代和90年代,Bjarne Strou ...

  4. pxe网络安装操作系统 原理与详细过程

    摘要:在实际工作中,我们经常会遇到这样的情况:想要安装Linux但是计算机不带光驱或软驱,或者是笔记本配置的非标准的软驱和光驱,如1394接口,USB接口等,在Linux安装时所引导的Linux内核一 ...

  5. 2013杭州网络赛C题HDU 4640(模拟)

    The Donkey of Gui Zhou Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/O ...

  6. FZU 1686 神龙的难题 DLX反复覆盖

    DLX反复覆盖: 须要一个A*函数剪支 Problem 1686 神龙的难题 Accept: 462    Submit: 1401 Time Limit: 1000 mSec    Memory L ...

  7. 跟我一起学extjs5(25--模块Form的自己定义的设计[3])

    跟我一起学extjs5(25--模块Form的自己定义的设计[3])         自己定义的Form已经能够执行了,以下改一下配置,把Form里面的FieldSet放在Tab之下.改动一下Modu ...

  8. html 中的块级元素 内联元素

    上一个礼拜,做crm项目,使用的大部分都是js,nodejs,ajax 的内容,但是今天我想写写关于html中的块级元素和内联元素 的东西. 首先,html 中的块级元素 内联元素 我们可以看到,这两 ...

  9. 【IE】trim()方法失效

    今天用了$.ajax异步提交,结果在ie8里面报错了,说不支持此对象,找了半天没找到什么问题. 后来发现是我data里面的参数传递,里面有个参数用到了trim()方法,这个方法在ie8里面是失效的. ...

  10. Objective-C开发编码规范:4大方面解决开发中的规范性问题

    Objective-C 编码规范,内容来自苹果.谷歌的文档翻译,自己的编码经验和对其它资料的总结. 概要 Objective-C 是一门面向对象的动态编程语言,主要用于编写 iOS 和 Mac 应用程 ...