pomelo-global-channel-plugin的使用

2016年12月17日 · 557 字 · 3 分钟

pomelo应用中channel默认是不可以跨进程的,来看一下现在服务器的配置:

{
  "development": {
    "connector": [
      {
        "id": "connector",
        "host": "127.0.0.1",
        "port": 3150,
        "clientHost": "127.0.0.1",
        "clientPort": 3010,
        "frontend": true
      }
    ],
    "chat": [
      {
        "id": "chat-1",
        "host": "127.0.0.1",
        "port": 4001
      },
      {
        "id": "chat-2",
        "host": "127.0.0.1",
        "port": 4002
      }
    ]
  }
}

可以看到使用了两台chat服务器,该服务器进行具体的消息接收以及消息推送。

在聊天时,由于后端chat服务器不止一台,所以后面通过app.get(‘channelService’)时发现channel没同步,查阅文档发现,pomelo提供的pomelo-global-channel-plugin插件,用来在app生命周期中管理全局channel。

可以说,这个插件目前可以解决我们的问题了。

安装

npm install pomelo-globalchannel-plugin --save

该插件依赖redis来实现跨服务器数据存储。

配置

config目录下新建 redis.json文件

{
  "development": {
    "host": "localhost",
    "port": 6379
  },
  "production": {
    "host": "localhost",
    "port": 6379
  }
}

编码

app.js

var pomelo = require('pomelo');
var globalChannel = require('pomelo-globalchannel-plugin');
var path = require('path');
/**
 * Init app for client.
 */
var app = pomelo.createApp();
app.set('name', 'demo');

// app configuration
app.configure('production|development', 'connector|chat', function() {
  app.set('connectorConfig',
    {
      connector: pomelo.connectors.sioconnector,
      //websocket, htmlfile, xhr-polling, jsonp-polling, flashsocket
      transports: ['websocket'],
      heartbeats: true,
      closeTimeout: 60,
      heartbeatTimeout: 60,
      heartbeatInterval: 25
    });
});
// global channel
app.loadConfig('redis', path.resolve('./config/redis.json'));
// redis
app.use(globalChannel, {
  globalChannel: app.get('redis')
});
// start app
app.start();

process.on('uncaughtException', function(err) {
  console.error(' Caught exception: ' + err.stack);
});

app/servers/chat/remote/remote.js

rpc路由为chat.remote.[method]

/**
 * @author xialeistduio<[email protected]>
 * @date 16-12-17
 */
'use strict';
module.exports = function(app) {
  return new Handler(app);
};

var Handler = function(app) {
  this.app = app;
  this.channel = app.get('globalChannelService');
};
/**
 * 加入聊天室
 * @param channelName
 * @param userId
 * @param connectorServerId
 * @param callback
 */
Handler.prototype.join = function(channelName, userId, connectorServerId, callback) {
  this.channel.add(channelName, userId, connectorServerId);
  var param = {
    userId: userId
  };
  this.channel.pushMessage('connector', 'onJoin', param, channelName);
  callback();
};
/**
 * 退出聊天室
 * @param channelName
 * @param userId
 * @param connectorServerId
 * @param callback
 */
Handler.prototype.leave = function(channelName, userId, connectorServerId, callback) {
  this.channel.leave(channelName, userId, connectorServerId);
  var param = {
    userId: userId
  };
  this.channel.pushMessage('connector', 'onLeave', param, channelName);
  callback();
};

可以看到使用的是globalChannelService,通过add,leave方法实现用户加入,退出channel。

app/servers/connector/handler/handler.js

前端路由为connector.handler.[method]

module.exports = function(app) {
  return new Handler(app);
};

var Handler = function(app) {
  this.app = app;
  this.sessionService = app.get('sessionService');
};
/**
 * 进入聊天
 * @param msg
 * @param session
 * @param next
 */
Handler.prototype.login = function(msg, session, next) {
  // 参数检测
  if (!msg.userId || !msg.lessonId) {
    return next(null, {code: 1, message: '缺少参数'});
  }
  // 是否已登录
  if (!!this.sessionService.getByUid(msg.userId)) {
    return next(null, {code: 1, message: '用户已登录'});
  }

  var self = this;
  session.bind(msg.userId);
  session.set('lessonId', msg.lessonId);
  session.push('lessonId', function(e) {
    e && console.error(e);
  });
  // 用户退出监听
  session.on('closed', function(session) {
    self.app.rpc.chat.remote.leave(session, session.get('lessonId'), session.uid, self.app.get('serverId'), null);
  });
  // 加入聊天室
  self.app.rpc.chat.remote.join(session, msg.lessonId, msg.userId, this.app.get('serverId'), function() {
    next(null, {code: 0, message: '登录成功'});
  });
};

app/servers/chat/handler/handler.js

前端路由为chat.handler.[method]

/**
 * @author xialeistduio<[email protected]>
 * @date 16-12-17
 */
'use strict';
module.exports = function(app) {
  return new Handler(app);
};

var Handler = function(app) {
  this.app = app;
  this.channel = app.get('globalChannelService');
};
/**
 * 发送消息
 * @param msg
 * @param session
 * @param next
 */
Handler.prototype.send = function(msg, session, next) {
  if (session.uid === null) {
    return next(null, {code: 1, message: '用户未登录'});
  }
  var lessonId = session.get('lessonId');
  // 发送消息
  msg.userId = session.uid;
  this.channel.pushMessage('connector', 'onMessage', msg, lessonId, null, function() {
    next(null, {message: '发送成功', code: 0});
  });
};

单元测试

本测试使用mocha以及should进行

安装依赖

由于单元测试在nodejs环境执行,而官方的pomelo客户端是浏览器的,所以需要下载nodejs版本的

npm install x.pomelo-client --save-dev

测试脚本

/**
 * @author xialeistduio<[email protected]>
 * @date 16-12-17
 */
'use strict';
var should = require('should');
var pomelo = require('x.pomelo-client');
describe('pomelo', function() {
  this.timeout(60000);
  it('connector::connect', function(done) {
    pomelo.init({
      host: 'localhost',
      port: 3010,
      log: true
    }, function() {
      done();
    });
  });
  it('connector::login', function(done) {
    pomelo.on('onJoin', function(data) {
      if (data.userId === 'xialei') {
        done();
      }
    });
    pomelo.request('connector.handler.login', {userId: 'xialei', lessonId: 4}, function(data) {
      should(data.code).be.exactly(0);
    });
  });
  it('chat::send', function(done) {
    pomelo.on('onMessage', function(data) {
      if (data.type === 1 && data.content === '哈哈') {
        done();
      }
    });
    pomelo.request('chat.handler.send', {type: 1, content: '哈哈'}, function(data) {
      should(data.code).be.exactly(0);
    });
  });
});

执行测试

mocha