前面的一片文章分析了master服务器的启动:
主要就是载入和启动组件,而且master的具体工作还是在其的console,对于master究竟在干些什么东西,并没有看的太多,以后涉及到了再细看吧。。那么这篇文章就来看看其余的server是怎么启动的,顺便还要看看他们是怎么进行工作的。。。。
好吧,还是首先从application的创建开始吧:
Pomelo.createApp = function (opts) {
var app = application;
app.init(opts); //初始化函数
self.app = app;
return app;
};
这这里,application的init过程会与master有差异,来细看:
Application.init = function(opts) {
opts = opts || {};
this.loaded = []; // loaded component list
//用于保存所有的组件
this.components = {}; // name -> component map
//调用set方法保存的参数将会保存在这个地方
this.settings = {}; // collection keep set/get
this.set('base', opts.base);
//事件对象
this.event = new EventEmitter(); // event object to sub/pub events
// current server info
this.serverId = null; // current server id
this.serverType = null; // current server type
this.curServer = null; // current server info
// global server infos
this.master = null; // master server info
//用于保存所有的server,id与info的键值对
this.servers = {}; // current global server info maps, id -> info
this.serverTypeMaps = {}; // current global type maps, type -> [info]
this.serverTypes = []; // current global server type list
appUtil.defaultConfiguration(this); //默认配置
this.state = STATE_INITED; //将当前的状态修改为已经初始化
logger.info('application inited: %j', this.getServerId());
};
当然这里还是看不出有什么区别的,但是在defaultConfiguration里面就会比较大的不同了:
module.exports.defaultConfiguration = function (app) {
var args = parseArgs(process.argv); //这里process.argv保存的是运行使用的命令,是一个数组例如[ 'node', '/home/fjs/Desktop/pomelo/game-server/app.js' ]
/*
[ '/usr/local/bin/node',
'/home/fjs/Desktop/pomelo/game-server/app.js',
'env=development',
'id=chat-server-2',
'host=127.0.0.1',
'port=6051',
'serverType=chat' ]
*/
//最后args如以下形式:
//master { main: '/home/fjs/Desktop/pomelo/game-server/app.js' }
/*{ main: '/home/fjs/Desktop/pomelo/game-server/app.js',
env: 'development',
id: 'connector-server-1',
host: '127.0.0.1',
port: 4050,
clientPort: 3050,
frontend: 'true',
serverType: 'connector' }*/
setupEnv(app, args); //设置当前环境的配置,是开发,部署等
loadMaster(app); //加载master服务器的配置
loadServers(app); //加载server的配置
processArgs(app, args);
configLogger(app);
};
这里最主要的不同就是server的启动使用的命令参数会不一样,会多一些server的信息,例如server的类型等,具体有什么不一样在上面的注释中已经标出来了(在前面的文章里面也有涉及到说这些参数是怎么搞出来的,说白了就是从配置文件中读取出来的)。。。另外有不太一样的地方就是processArgs的部分:
var processArgs = function(app, args){
var serverType = args.serverType || 'master'; //如果没有serverType,那么就是master服务器了
var serverId = args.id || app.getMaster().id; //获取服务器的id
app.set('main', args.main, true); //将运行信息保存起来,
//master { main: '/home/fjs/Desktop/pomelo/game-server/app.js' }
/*{ main: '/home/fjs/Desktop/pomelo/game-server/app.js',
env: 'development',
id: 'connector-server-1',
host: '127.0.0.1',
port: 4050,
clientPort: 3050,
frontend: 'true',
serverType: 'connector' }*/
app.set('serverType', serverType, true);
app.set('serverId', serverId, true);
if(serverType !== 'master') { //保存当前服务器类型
app.set('curServer', args, true);
} else {
app.set('curServer', app.getMaster(), true);
}
};
因为此刻的server类型不一样了,所以自然与master就会有很多的不同,他们的参数在上面的注释中也有了。。。那么在application的创建过程有什么不一样的地方已经看出来了。。那么接下来就是application的启动过程了。。。
至于说application的启动过程,主要还是分为两部分吧,首先是载入组件,接着是启动组件,那么对于一般的server,这里在组件的载入过程就已经很大的不一样了。。因为要载入的组件有很大的差异。。。
Application.start = function(cb) {
if(this.state > STATE_INITED) { //正常情况应该是等于这个
utils.invokeCallback(cb, new Error('application has already start.'));
return;
}
appUtil.loadDefaultComponents(this); //用来加载默认的组件
var self = this;
//调用这些组件的start,开启这些组件
appUtil.optComponents(this.loaded, 'start', function(err) {
self.state = STATE_START;
if(err) {
utils.invokeCallback(cb, err);
} else {
logger.info('%j enter after start...', self.getServerId());
self.afterStart(cb);
}
});
};
当然上述的部分还是一样的,显示载入一些组价,然后再启动这些组件。。。。那么来具体看看普通的server载入了那些组件吧:
//为application加载默认的组件
module.exports.loadDefaultComponents = function(app) {
var pomelo = require('../pomelo');
// load system default components
if (app.serverType === 'master') {
app.load(pomelo.master, app.get('masterConfig')); //get得到的为null
//这里pomelo.master在pomelo里面定义了,会载入components里面的master文件,这里的master其实是包装器,真正的master会在里面加载
} else { //普通的server 需要载入的组件
app.load(pomelo.proxy, app.get('proxyConfig'));
if(app.getCurServer().port) {
app.load(pomelo.remote, app.get('remoteConfig'));
}
if(app.isFrontend()) {
app.load(pomelo.connection, app.get('connectionConfig')); //connection组件用于维护连接,比如说连接的状态
app.load(pomelo.connector, app.get('connectorConfig')); //用于从客户端接收socket,并为其分配session等
app.load(pomelo.session, app.get('sessionConfig')); //用于维护管理session
app.load(pomelo.protobuf, app.get('protobufConfig'));
app.load(pomelo.scheduler, app.get('schedulerConfig'));
}
app.load(pomelo.localSession, app.get('localSessionConfig'));
app.load(pomelo.channel, app.get('channelConfig')); //channel的维护
app.load(pomelo.server, app.get('serverConfig')); //当前server的具体一些东西,例如一些用户定义的handler等
if(app.get('globalChannelConfig')) {
app.load(pomelo.globalChannel, app.get('globalChannelConfig'));
}
}
app.load(pomelo.monitor, app.get('monitorConfig'));
};
上面可以很明显的看出master服务器和普通的server之间载入的组件有什么区别,至于说一些重要的组件他们是干什么的在上面的注释中已经较为简单的写出来了。。。
其实想要知道server究竟是怎么运行的,只要搞清楚这些组件究竟干了些什么工作就可以啦。。。
那么我们首先来看看connector在干些什么吧,首先来看看他的构造函数吧:
var Component = function(app, opts) {
opts = opts || {};
this.app = app;
this.connector = getConnector(app, opts); //创建具体的connector,用于监听端口
this.encode = opts.encode; //这里一般情况下都是null
this.decode = opts.decode;
if(opts.useDict) {
app.load(pomelo.dictionary, app.get('dictionaryConfig'));
}
// component dependencies
this.server = null;
this.session = null;
this.connection = null;
};
进行了一些初始化,然后最主要的就是创建具体的connector,用它来完成监听端口等事情。。那么接下来来看看这个具体的connector是怎么创建的吧:
var getConnector = function(app, opts) {
var connector = opts.connector; //一般情况下这个也是null
if(!connector) {
return getDefaultConnector(app); //创建默认的connector
}
if(typeof connector !== 'function') {
return connector;
}
var curServer = app.getCurServer();
return connector(curServer.clientPort, curServer.host, opts);
};
好吧,接下来来看看这个默认的connector是怎么创建的吧:
//创建默认的connector,sio
var getDefaultConnector = function(app) {
var DefaultConnector = require('../connectors/sioconnector'); //构造函数
var curServer = app.getCurServer();
return new DefaultConnector(curServer.clientPort, curServer.host); //创建connector
};
这里可以看到,其实创建的是sioconnector,那么接下来来具体看看它吧:
var Connector = function(port, host) {
if (!(this instanceof Connector)) {
return new Connector(port, host);
}
EventEmitter.call(this); //让其可以支持事件
this.port = port; //端口
this.host = host; //host
};
util.inherits(Connector, EventEmitter);
构造函数还是很简单的,无非就是保存一下host和port参数而已。。。好像到这里为止并没有看出什么门路来,好像也没有做什么事情嘛。。好吧,我们来看看connector的start吧:
//其实这里的start主要还是进行一些组件的验证,并没有做太多的事情
Component.prototype.start = function(cb) { //这里的start主要是用于检测组件是否正确,并没有启动connector,延迟到了afterstart
this.server = this.app.components.__server__;
this.session = this.app.components.__session__;
this.connection = this.app.components.__connection__;
// check component dependencies
if(!this.server) {
process.nextTick(function() {
cb(new Error('fail to start connector component for no server component loaded'));
});
return;
}
if(!this.session) {
process.nextTick(function() {
cb(new Error('fail to start connector component for no session component loaded'));
});
return;
}
process.nextTick(cb);
};
看上面的代码,好像也不能看出什么门路来,好像connector的启动也并没有做什么事情,主要就是进行了一些组件的验证。。。不过接下来来看看connector的afterStart。。就知道其实大部分的工作是在这里进行的,afterStart在前面的文章漏掉了:当组件被启动完成之后,会调用这些组件的afterStart函数。。。那么我们来看看connector的afterStart干了什么事情吧:
//这部分主要是用于启动具体的connector,并设置一些它的事件处理
Component.prototype.afterStart = function(cb) {
var self = this;
this.connector.start(cb); //启动connector
this.connector.on('connection', function(socket) { //为connector绑定事件处理函数
bindEvents(self, socket);
}); //on connection end
};
看代码可以知道,这里会执行sioconnector的start,并为其设置事件处理函数,那么先来看看这个start过程吧:
//用于监听特定的端口,并设置一些事件
Connector.prototype.start = function(cb) {
var self = this;
// issue https://github.com/NetEase/pomelo-cn/issues/174
this.wsocket = sio.listen(this.port, { //用于监听配置的端口
transports: [
'flashsocket', 'websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling'
]
});
this.wsocket.set('log level', 1);
this.wsocket.sockets.on('connection', function (socket) { //当有新的连接建立的时候
var siosocket = new SioSocket(curId++, socket);
self.emit('connection', siosocket); //本身出发connector事件,用于通知外面的connector
siosocket.on('closing', function(reason) {
if(reason === 'kick') {
siosocket.send({route: 'onKick'});
}
});
});
process.nextTick(cb);
};
哈,这部分代码就可以看到很多干货了吧,首先监听当前server配置的短裤,然后当有新的连接进来的时候,创建自己定义的siosocket(这里就不细讲它了,很简单),然后还要设置一些事件处理,同时还要自己触发connection事件,在前面的代码,可以知道connector的处理函数是:
this.connector.on('connection', function(socket) { //为connector绑定事件处理函数
bindEvents(self, socket);
}); //on connection end
说白了就是为刚刚建立的socket绑定一些事件处理,那么我们具体来看看都绑定了哪些事件处理吧:
var bindEvents = function(self, socket) {
if(self.connection) {
self.connection.increaseConnectionCount(); //增加当前connection的连接数目
}
//create session for connection
var session = getSession(self, socket); //为当前的socket分配session
var closed = false;
socket.on('disconnect', function() { //表示连接已经断开
if(closed) {
return;
}
closed = true; //表示当前的connection已经关闭了
if(self.connection) {
self.connection.decreaseConnectionCount(session.uid);
}
});
socket.on('error', function() { //连接发生了错误
if(closed) {
return;
}
closed = true;
if(self.connection) {
self.connection.decreaseConnectionCount(session.uid);
}
});
// new message
socket.on('message', function(msg) {//表示接收到数据
var dmsg = msg;
if(self.decode) {
dmsg = self.decode(msg);
} else if(self.connector.decode) {
dmsg = self.connector.decode(msg);
}
if(!dmsg) {
// discard invalid message
return;
}
handleMessage(self, session, dmsg); //这里是调用server具体的handler来处理收到的这些数据
}); //on message end
};
哈,这部分干活很多吧,尤其是后面的那行代码,还是从头开始说起吧,首先是增加当前的connection计数,然后设置一些基本的事件函数,例如disconnect,error等,这些都很容易联想到,最后比较重要的就是设置了message的处理函数,也就是当收到了数据之后应该怎么做,其实是调用当前server定义的一些handler来进行处理。。那么我们来看看handleMessage这个函数的定义吧:
var handleMessage = function(self, session, msg) {
var type = checkServerType(msg.route);
if(!type) {
logger.error('invalid route string. route : %j', msg.route);
return;
}
self.server.handle(msg, session.mockLocalSession(), function(err, resp) { //用当前的server组件的handler来处理收到的
if(resp) {
if(!msg.id) {
logger.warn('try to response to a notify: %j', msg.route);
return;
} //发送返回的数据
self.send(msg.id, msg.route, resp, [session.id], {isResponse: true}, //发送返回的数据
function() {});
}
});
};
这里其实调用的是server组件的handle方法来处理收到的数据的。。。
好像应该接下来继续写是怎么处理这个数据的。。但是好像写的比较多了,还是留个下一篇文章吧。、、、
那么现在我们可以看到的就是:
(1)首先由connector建立连接并接收数据,而且还要分配session等。。
(2)由server组件的handle方法来处理这些数据。。。