// servers.json{ "development": { "connector": [{ "id": "connector-server-1", "host": "127.0.0.1", "port": 4050, "clientPort": 3050, "frontend": true }, { "id": "connector-server-2", "host": "127.0.0.1", "port": 4051, "clientPort": 3051, "frontend": true }, { "id": "connector-server-3", "host": "127.0.0.1", "port": 4052, "clientPort": 3052, "frontend": true }], "chat": [{ "id": "chat-server-1", "host": "127.0.0.1", "port": 6050 }, { "id": "chat-server-2", "host": "127.0.0.1", "port": 6051 }, { "id": "chat-server-3", "host": "127.0.0.1", "port": 6052 }], "gate": [{ "id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true }] }, "production": { "connector": [{ "id": "connector-server-1", "host": "127.0.0.1", "port": 4050, "clientPort": 3050, "frontend": true }, { "id": "connector-server-2", "host": "127.0.0.1", "port": 4051, "clientPort": 3051, "frontend": true }, { "id": "connector-server-3", "host": "127.0.0.1", "port": 4052, "clientPort": 3052, "frontend": true }], "chat": [{ "id": "chat-server-1", "host": "127.0.0.1", "port": 6050 }, { "id": "chat-server-2", "host": "127.0.0.1", "port": 6051 }, { "id": "chat-server-3", "host": "127.0.0.1", "port": 6052 }], "gate": [{ "id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true }] }}
// cocos2d-jsvar pomeloChat = function() { var pomelo = window.pomelo; var route = 'gate.gateHandler.queryEntry'; var uid = "uid"; var rid = "rid"; var username = "username"; // 请求连接gate服务器 pomelo.init({ host: "192.168.33.192", port: 3014, log: true }, function() { // 连接成功之后,向gate服务器请求ip和port pomelo.request(route, { uid: uid }, function(data) { // 断开与gate服务器之间的连接 pomelo.disconnect(); // 使用gate服务器返回的ip和port请求连接connector服务器 pomelo.init({ host: data.host, port: data.port, log: true }, function() { // 连接成功之后,向connector服务器发送登录请求 var route = "connector.entryHandler.enter"; pomelo.request(route, { username: username, rid: rid }, function(data) { // 登录成功之后向聊天服务器发送聊天内容 cc.log(JSON.stringify(data)); chatSend(); }); }); }); // 客户端接收广播消息,并将消息并显示即可。 pomelo.on('onChat', function(data) { cc.log(data.from, data.target, data.msg); }); }); function chatSend() { var route = "chat.chatHandler.send"; var target = "*"; var msg = "msg" pomelo.request(route, { rid: rid, content: msg, from: username, target: target }, function(data) { cc.log(JSON.stringify(data)); }); };}
// --------------------------------------------------------------------------------// app.js// --------------------------------------------------------------------------------var pomelo = require('pomelo');var routeUtil = require('./app/util/routeUtil');/** * Init app for client. */var app = pomelo.createApp();app.set('name', 'chatofpomelo-websocket');// app configurationapp.configure('production|development', 'connector', function() { app.set('connectorConfig', { connector: pomelo.connectors.hybridconnector, heartbeat: 3, useDict: true, useProtobuf: true });});app.configure('production|development', 'gate', function() { app.set('connectorConfig', { connector: pomelo.connectors.hybridconnector, useProtobuf: true });});// app configureapp.configure('production|development', function() { // route configures app.route('chat', routeUtil.chat); // filter configures app.filter(pomelo.timeout());});// start appapp.start();process.on('uncaughtException', function(err) { console.error(' Caught exception: ' + err.stack);});
// --------------------------------------------------------------------------------// routeUtil.js// --------------------------------------------------------------------------------var exp = module.exports;var dispatcher = require('./dispatcher');exp.chat = function(session, msg, app, cb) { var chatServers = app.getServersByType('chat'); if (!chatServers || chatServers.length === 0) { cb(new Error('can not find chat servers.')); return; } var res = dispatcher.dispatch(session.get('rid'), chatServers); cb(null, res.id);};
// --------------------------------------------------------------------------------// gateHandler.js// --------------------------------------------------------------------------------var dispatcher = require('../../../util/dispatcher');module.exports = function(app) { return new Handler(app);};var Handler = function(app) { this.app = app;};var handler = Handler.prototype;/** * Gate handler that dispatch user to connectors. * * @param {Object} msg message from client * @param {Object} session * @param {Function} next next stemp callback * */// 入口函数handler.queryEntry = function(msg, session, next) { var uid = msg.uid; if (!uid) { next(null, { code: 500 }); return; } // 获得所有的connectors // get all connectors var connectors = this.app.getServersByType('connector'); if (!connectors || connectors.length === 0) { next(null, { code: 500 }); return; } // 从connectors中分配一个connector // select connector var res = dispatcher.dispatch(uid, connectors); // 将分配的connector的ip和端口返回给客户端 next(null, { code: 200, host: res.host, port: res.clientPort });};
// --------------------------------------------------------------------------------// dispatcher.js// --------------------------------------------------------------------------------var crc = require('crc');// 根据用户uid对总的connector取模,作为下标返回对应的connectormodule.exports.dispatch = function(uid, connectors) { var index = Math.abs(crc.crc32(uid)) % connectors.length; return connectors[index];};// --------------------------------------------------------------------------------// connector.js// --------------------------------------------------------------------------------module.exports = function(app) { return new Handler(app);};var Handler = function(app) { this.app = app;};var handler = Handler.prototype;/** * New client entry chat server. * * @param {Object} msg request message * @param {Object} session current session object * @param {Function} next next stemp callback * @return {Void} */handler.enter = function(msg, session, next) { var self = this; var rid = msg.rid; var uid = msg.username + '*' + rid // 获得一个session var sessionService = self.app.get('sessionService'); // 重复登录 //duplicate log in if (!!sessionService.getByUid(uid)) { next(null, { code: 500, error: true }); return; } // 用户进入聊天室后,服务器端首先需要完成用户的session注册 session.bind(uid);// bind调用,给session绑定uid; session.set('rid', rid); session.push('rid', function(err) {// push方法,将设置的settings的值同步到原始session中 if (err) { console.error('set rid for session service failed! error is : %j', err.stack); } }); // 同时绑定用户离开事件 session.on('closed', onUserLeave.bind(null, self.app)); // 另外,服务器端需要通过调用rpc方法将用户加入到相应的channel中; // 同时在rpc方法中,服务器端需要将该用户的上线消息广播给其他用户, // 最后服务器端向客户端返回当前channel中的用户列表信息。 //put user into channel self.app.rpc.chat.chatRemote.add(session, uid, self.app.get('serverId'), rid, true, function(users) { next(null, { users: users }); });};/** * User log out handler * * @param {Object} app current application * @param {Object} session current session object * */// 用户在退出聊天室时,必须完成一些清理工作。// 在session断开连接时,通过rpc调用将用户从channel中移除。// 在用户退出前,还需要将自己下线的消息广播给所有其他用户。var onUserLeave = function(app, session) { if (!session || !session.uid) { return; } app.rpc.chat.chatRemote.kick(session, session.uid, app.get('serverId'), session.get('rid'), null);};
// --------------------------------------------------------------------------------// ChatRemote.js// --------------------------------------------------------------------------------module.exports = function(app) { return new ChatRemote(app);};var ChatRemote = function(app) { this.app = app; this.channelService = app.get('channelService');};/** * Add user into chat channel. * * @param {String} uid unique id for user * @param {String} sid server id * @param {String} name channel name * @param {boolean} flag channel parameter * */// 加入聊天室ChatRemote.prototype.add = function(uid, sid, name, flag, cb) { var channel = this.channelService.getChannel(name, flag); var username = uid.split('*')[0]; var param = { route: 'onAdd', user: username }; channel.pushMessage(param); if (!!channel) { channel.add(uid, sid); } cb(this.get(name, flag));};/** * Get user from chat channel. * * @param {Object} opts parameters for request * @param {String} name channel name * @param {boolean} flag channel parameter * @return {Array} users uids in channel * */// 从聊天室中获取用户ChatRemote.prototype.get = function(name, flag) { var users = []; var channel = this.channelService.getChannel(name, flag); if (!!channel) { users = channel.getMembers(); } for (var i = 0; i < users.length; i++) { users[i] = users[i].split('*')[0]; } return users;};/** * Kick user out chat channel. * * @param {String} uid unique id for user * @param {String} sid server id * @param {String} name channel name * */// 将用户移除聊天室ChatRemote.prototype.kick = function(uid, sid, name, cb) { var channel = this.channelService.getChannel(name, false); // leave channel if (!!channel) { channel.leave(uid, sid); } var username = uid.split('*')[0]; var param = { route: 'onLeave', user: username }; channel.pushMessage(param); cb();};
// --------------------------------------------------------------------------------// chatHandler.js// --------------------------------------------------------------------------------var chatRemote = require('../remote/chatRemote');module.exports = function(app) { return new Handler(app);};var Handler = function(app) { this.app = app;};var handler = Handler.prototype;/** * Send messages to users * * @param {Object} msg message from client * @param {Object} session * @param {Function} next next stemp callback * */handler.send = function(msg, session, next) { // 客户端向服务端发起聊天请求,请求消息包括聊天内容,发送者和发送目标信息。 // 消息的接收者可以聊天室里所有的用户,也可以是某一特定用户。 var rid = session.get('rid'); var username = session.uid.split('*')[0]; var channelService = this.app.get('channelService'); var param = { msg: msg.content, from: username, target: msg.target }; channel = channelService.getChannel(rid, false); // 如果发送目标是所有用户,服务器端首先会选择channel中的所有用户, // 然后向channel发送消息,最后前端服务器就会将消息分别发送给channel中取到的用户 //the target is all users if (msg.target == '*') { channel.pushMessage('onChat', param); } // 如果发送目标只是某一特定用户,发送过程和之前完全一样, // 只是服务器端首先从channel中选择的只是一个用户,而不是所有用户。 //the target is specific user else { var tuid = msg.target + '*' + rid; var tsid = channel.getMember(tuid)['sid']; channelService.pushMessageByUids('onChat', param, [{ uid: tuid, sid: tsid }]); } next(null, { route: msg.route });};
其中,err是前面流程中发生的异常;resp是前面流程传递过来,需要返回给客户端的响应信息。其他参数与前面的handler一样。
十八 Session
在pomelo框架中,有这三个session的概念,同时又有两个service:SessionService和BackendSessionService。
1 Session
Session的是一个客户端连接的抽象,它的大致字段如下:
{ id : <session id> // readonly frontendId : <frontend server id> // readonly uid : <bound uid> // readonly settings : <key-value map> // read and write __socket__ : <raw_socket> __state__ : <session state> // ...}
{ id : <session id> // readonly frontendId : <frontend server id> // readonly uid : <bound uid> // readonly settings : <key-value map> // read and write}
参考:https://github.com/NetEase/pomelo/wiki/Home-in-Chinese
原文链接:http://blog.csdn.net/xufeng0991/article/details/45029171