// 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-js
var 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 configuration
app.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 configure
app.configure('production|development', function() {
// route configures
app.route('chat', routeUtil.chat);
// filter configures
app.filter(pomelo.timeout());
});
// start app
app.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取模,作为下标返回对应的connector
module.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 : // readonly
frontendId : // readonly
uid : // readonly
settings : // read and write
__socket__ :
__state__ :
// ...
}
{
id : // readonly
frontendId : // readonly
uid : // readonly
settings : // read and write
}
参考:https://github.com/NetEase/pomelo/wiki/Home-in-Chinese
原文链接:http://blog.csdn.net/xufeng0991/article/details/45029171