chatofPomelo game-server解析

chatOfPomelo是一个聊天室程序,笔者将对chat的服务端game-server进行分析
首先来看它的文件结构
chatofPomelo game-server解析_第1张图片
大的结构与hellopomelo一致,都有app,config,logs,node_modules,app.js.
有了hello的经验,我们直奔主题config/servers.json
这里配置着我们自定义的服务器配置

{
    "development":{ "connector":[ {"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true} ], "chat":[ {"id":"chat-server-1", "host":"127.0.0.1", "port":6050} ], "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} ], "chat":[ {"id":"chat-server-1", "host":"127.0.0.1", "port":6050} ], "gate":[ {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true} ] } }

这里看出来对于development和production环境都作了同样的配置
这次的聊天室是由三个自定义的服务组成
gate,chat,connector
gate– 前端网关服务,用于最初与服务器的连接,并分配connector给客户端
connector– 前端连接服务器,用于与客户端保持websocket长连接,并转发后端服务器处理的协议
chat– 后端聊天服务器,客户端不能与它进行连接,它处理好的逻辑通过connector传递给客户端
看看3个服务的目录结构

注意到gate,connector作为前端服务器,他们都有handler文件夹
而chat后端服务器,不仅有handler还有remote文件夹
在pomelo中,用户自定义的handler文件夹表示要处理与客户端连接相关的事件
而remote文件则表示处理服务器的rpc调用
handler文件夹里的文件会被作为组件加载.且放入到一个route中,以便于客户端向pomelo请求gate.gateHandler.queryEntry这样的请求时,会被精确定位到
gate/handler/gateHandler.js中的queryEntry接口
看看gateHandler.js的代码

module.exports = function(app) { // 导出handler类
    return new Handler(app);
};

var Handler = function(app) {
    this.app = app;
};

var handler = Handler.prototype;

handler.queryEntry = function(msg, session, next) { // 客户端访问gate的唯一接口
    var uid = msg.uid;
    if(!uid) {
        next(null, {
            code: 500
        });
        return;
    }
    // get all connectors
    var connectors = this.app.getServersByType('connector');
    if(!connectors || connectors.length === 0) {
        next(null, {
            code: 500
        });
        return;
    }
    // here we just start `ONE` connector server, so we return the connectors[0] 
    var res = connectors[0];
    // next是向客户端返回消息
    next(null, {
        code: 200,
        host: res.host,
        port: res.clientPort
    });
};

entryHandler.js(这个文件在connector目录底下,不管它叫什么名字,都是connector的模块)

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

var Handler = function(app) {
        this.app = app;
};

var handler = Handler.prototype;
// 客户端访问connector的入口
handler.enter = function(msg, session, next) {
    var self = this;
    var rid = msg.rid;
    var uid = msg.username + '*' + rid
    // sessionService是pomelo提供的默认服务,维护session信息
    var sessionService = self.app.get('sessionService');

    //duplicate log in
    if( !! sessionService.getByUid(uid)) {
        next(null, {
            code: 500,
            error: true
        });
        return;
    }
    session.bind(uid);
    session.set('rid', rid);
    session.push('rid', function(err) {
        if(err) {
            console.error('set rid for session service failed! error is : %j', err.stack);
        }
    });
    session.on('closed', onUserLeave.bind(null, self.app));

    //通过rpc调用把玩家加入到chat服务中,并返回玩家列表给客户端
    self.app.rpc.chat.chatRemote.add(session, uid, self.app.get('serverId'), rid, true, function(users){
        next(null, {
            users:users
        });
    });
};
// 玩家关闭浏览以后,通过rpc调用通知chatRemote把这个玩家删除
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);
};

chat分2部分,一部分是handler,另一部分是remote
handler说明它要处理客户端的信息
remote说明它要处理来自后端服务器的信息
由于我们的connecotr用到了chat的add以及kick,因此我们先看remote的内容
chatremote.js

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

var ChatRemote = function(app) {
    this.app = app;
    this.channelService = app.get('channelService'); // channel也是pomelo的默认服务器
};

// 把玩家添加到指定的channel中 uid玩家id sid服务器id name 频道名 flag 频道标识符
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));
};

// 从channel中获取玩家信息 name 频道名 flag 频道参数 返回玩家列表
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;
};

//把玩家从channel中踢出
ChatRemote.prototype.kick = function(uid, sid, name) {
    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);
};

从代码上看出都是与pomelo的内部组件打交道的方法,代码简单,不过这里的所有发往客户端的message都是通过channel来完成的.channel又是通过connector来完成,所以此服务器与客户端并不直接相联,都通过connector来中转

最后是 chat.Handler,在pomelo中,handler都是处理客户端消息的地方,不过由于chat不是前端服务器,这里的send事件是由connector捕捉到以后route过来的,而不是客户端直接与chat服务器进行的连接.

var chatRemote = require('../remote/chatRemote');

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

var Handler = function(app) {
    this.app = app;
};

var handler = Handler.prototype;

// 处理收到的来自connecotr转发的客户端消息
handler.send = function(msg, session, next) {
    var rid = session.get('rid');  //获取玩家的填的channel id
    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);  // 通过channelid获取与此服务器对应的channel实例

    //the target is all users
    if(msg.target == '*') {
        channel.pushMessage('onChat', param);  // channel通过connector把消息发送到客户端
    }   
    else { //the target is specific user
        var tuid = msg.target + '*' + rid;
        var tsid = channel.getMember(tuid)['sid'];
        channelService.pushMessageByUids('onChat', param, [{
            uid: tuid,
            sid: tsid
        }]);
    }
    next(null, {
        route: msg.route
    });
};

最后我们来看一下整个网络结构图
chatofPomelo game-server解析_第2张图片
gate是唯一的,起到大门的作用.客户端首先发起连接是和gate产生的,由gate决定客户端与哪个connecotr连接,gate可以起到负载均衡的作用
这里可以有多个connector群.负责承载来自客户端的连接,并与后端的chatroom进行交互.
这里也可以有多个chatroom,每个chat可以对应到不同的connector.
后端的chatroom不与客户端直接连接

你可能感兴趣的:(服务器,chat,pomleo)