Pomelo Application
应用程序配置,如何配置Pomelo框架?
Pomelo可以配置各个组件的选项,加载配置文件,开启Pomelo的特性等,这些配置都是在game-server/app.js
文件中进行的。实际上在Pomelo的应用中有两个app.js
,一个是在game-server
目录下,一个是在web-server
目录下。game-server
下的app.js
是整个游戏服务器的入口和配置点。web-server
下的app.js
是Web服务器入口。
pomelo进程
pomelo框架是如何驱动的呢?
当应用启动后,使用pstree -au
得到进程树,可发现pomelo start
启动命令调用进程会创建子进程,子进程执行的是node app.js env=development
,然后这个子进程又会创建更多子进程,这些子进程执行的跟原进程同样的文件,只是多个更多的参数。
$ cd game-server
$ pomelo start
$ pstree -au
pomelo start
命令的进程直接创建的子进程实际是master服务器进程,由master服务器创建的子进程执行node <BasePath>/app.js env=development id=chat-server-1...
命令则是由master服务器创建的子进程,这些子进程也就是应用服务器。这里所有的进程都是在一台主机上,所有会存在父子关系,也就是说master进程是其它应用服务器的父进程。如果各个进程分布在不同的物理主机上的话,pomelo默认会使用ssh方式远程启动相应的服务器,那么master进程与应用程序进程不会再是父子关系。
使用pomelo start
命令后命令行工具pomelo
会检查start
后是否存在其它参数,比如是否需要daemon
后台守护进程的形式启动,是否指定env
环境等。若没有则会默认为其添加env=development
环境参数后启动node.js进程。
node <BasePath>/app.js env=development
此时pomelo start
命令就启动了app.js
脚本
$ cat /lib/util/appUtils.js
var setEnv = function(app, args){app.set(Constants.RESERVED.ENV, args.env || process.env.NODE_ENV || Constants.RESERVED.ENV_DEV, true);
};
使用pomelo start
命令启动pomelo应用时,若没有传入--env
参数则会先检查process.env.NODE_ENV
环境变量是否设置。若没有设置则默认为development
。若通过pomelo start --env production
方式启动则env为production
。
辅助命令
查看端口
$ netstat -tln
$ netstat -anp
搜索指定进程
$ netstat -anp | grep process_name
杀死指定PID的进程
$ kill -9 pid
游戏服务器启动流程
createApp
创建应用实例app.configure()
加载配置和默认component
组件master
服务器启动通过配置和启动参数启动其它服务器
$ vim game-server/app.js
//加载pomelo
let pomelo = require("pomelo");
//创建app实例
let app = pomelo.createApp();
//通过app这个上下文对框架的配置以及一些初始化操作
app.configure(<env>, <serverType>, function(){});
app.configure(...);
app.set(...);
app.route(...);
//启动应用
app.start();
例如:典型的启动文件包含内容
const pomelo = require('pomelo');//创建应用实例
const app = pomelo.createApp();//加载配置和组件
//app.configure(<env>, <serverType>, function(){});
//env:development|production
//serverType: master/gate/connector/...
app.configure("development|production", "connector", function(){//过滤器配置app.before(pomelo.filters.toobusy());//接口访问限制app.filter(pomelo.filters.serial()); // 配置内置过滤器: serialFilterapp.filter(pomelo.filters.time()); //开启conn日志,对应pomelo-admin模块下conn requestapp.rpcFilter(pomelo.rpcFilters.rpcLog());//开启rpc日志,对应pomelo-admin模块下rpc request//启动系统监控app.enable('systemMonitor');//注册admin module//enable systemMonotor后 注册的admin module才可使用var onlineUser = require('./app/modules/onlineUser');if (typeof app.registerAdmin === 'function') {app.registerAdmin(onlineUser, {app: app});}//加载配置app.loadConfig('mysql', app.getBase() + '/config/mysql.json');//配置路由app.route('chat', routeUtil.chat);//配置代理app.set('proxyConfig', {cacheMsg: true,interval: 30,lazyConnection: true,enableRpcLog: true});//远程配置app.set('remoteConfig', {cacheMsg: true,interval: 30});//设置内部connector组件: 心跳时长 通信协议app.set('connectorConfig',{connector: pomelo.connectors.hybridconnector,heartbeat: 30,useDict: true,useProtobuf: true,handshake: function (msg, cb) {cb(null, {});}});//设置变量app.set(key, value);//加载用户自定义组件 //组件导出的都是工厂函数,app可自动识别,讲其自身作为opt参数传递给组件,方便访问app上下文。app.load(helloWorldComponent, opt);//使用插件const statusPlugin = require('pomelo-status-plugin');app.use(statusPlugin, {status:{host: '127.0.0.1',port: 6379}});//启动应用app.start();
});process.on('uncaughtException', function(err){console.error('uncaughtException : ', err, err.stack());
});
Pemolo启动流程
应用初始化createApp
创建应用
- pomelo调用createApp()创建应用实例
const app = pomelo.createApp();
- pomelo的createApp调用中会调用app的init()方法完成对app的初始化
/*** Create an pomelo application.** @return {Application}* @memberOf Pomelo* @api public*/
Pomelo.createApp = function (opts) {var app = application;app.init(opts);self.app = app;return app;
};
- app会使用appUtil提供的defaultConfiguration来完成自己的初始化配置
/*** Initialize the server.** - setup default configuration*/
Application.init = function(opts) {opts = opts || {};this.loaded = []; // loaded component listthis.components = {}; // name -> component mapthis.settings = {}; // collection keep set/getvar base = opts.base || path.dirname(require.main.filename);this.set(Constants.RESERVED.BASE, base, true);this.event = new EventEmitter(); // event object to sub/pub events// current server infothis.serverId = null; // current server idthis.serverType = null; // current server typethis.curServer = null; // current server infothis.startTime = null; // current server start time// global server infosthis.master = null; // master server infothis.servers = {}; // current global server info maps, id -> infothis.serverTypeMaps = {}; // current global type maps, type -> [info]this.serverTypes = []; // current global server type listthis.lifecycleCbs = {}; // current server custom lifecycle callbacksthis.clusterSeq = {}; // cluster id seqenceappUtil.defaultConfiguration(this);this.state = STATE_INITED;logger.info('application inited: %j', this.getServerId());
};
- appUtil的defaultConfiguration会调用app的一些初始化方法,这些方法包括setEnv设置环境参数、loadMaster加载主服务器、loadServers加载应用服务器,parseArgs解析参数,configLogger配置日志。
/*** Initialize application configuration.*/
module.exports.defaultConfiguration = function(app) {var args = parseArgs(process.argv);setupEnv(app, args);loadMaster(app);loadServers(app);processArgs(app, args);configLogger(app);loadLifecycle(app);
};
- setEnv操作会将当前的env设定为development
- loadMaster调用会加载maseter服务器的配置信息
- loadServers会加载所有的应用服务器配置信息
- parseArgs是一个关键性的操作,由于
pomelo start
启动参数中仅仅指定了env,其它参数并未指定,此时pomelo认为目前启动的不是应用服务器而是master服务器。因此,当前进程将使用master的配置信息,并将自己的serverId、serverType等参数设置为master服务器所有的。实际上,对于应用服务器来说,如果启动的是应用服务器的话,node app.js
后可带有更多参数,包括id、serverType、port、clientPort等,这些参数在parseArgs这一步将会被处理,从而确定当前服务器的ID、类型等其它必须的配置信息。
5.执行完上诉操作后app进入INITED已初始化状态,同时pomelo的createApp返回。
当pomelo的createApp()方法返回后在app.js中接下来会对app进行一系列的配置,比如调用app.set()设置上下文变量的值,app.route()调用配置路由等。
应用程序配置
app.js
是运行Pomelo项目的入口,在app.js
文件中首先会创建一个app
实例,这个app
作为整个框架的配置上下文来使用,用户可以通过这个上下文,设置一些全局变量,加载配置信息等操作。
configure
使用app.configure
调用来配置
服务器的配置主要由configure()
方法完成,完整的app.configure
配置参数格式:
app.configure([env], [serverType], [function]);
参数 | 描述 |
---|---|
env |
运行环境,可设置为development 、production 、development|production 。
|
serverType |
服务器类型,设置后只会对当前参数类型服务器做初始化,不设置则对所有服务器执行初始化的function 。比如gate 、connector 、chat ...
|
function | 具体的初始化操作,内部可以些任何对框架的配置操作逻辑。 |
Application.configure = function (env, type, fn) {var args = [].slice.call(arguments);fn = args.pop();env = type = Constants.RESERVED.ALL;if(args.length > 0) {env = args[0];}if(args.length > 1) {type = args[1];}if (env === Constants.RESERVED.ALL || contains(this.settings.env, env)) {if (type === Constants.RESERVED.ALL || contains(this.settings.serverType, type)) {fn.call(this);}}return this;
};
更多地可以在configure
中针对不同的服务器、不同的环境,对框架进行不同的配置。这些配置包括设置一个上下文变量供应用使用,开启一些功能选项,配置加载一个自定义的组件component
,针对不同的服务器,配置过滤器filter
等配置操作。
loadConfig
例如:全局配置MySQL参数
$ vim game-server/config/mysql.json
{"development":{"host": "127.0.0.1","port": "3306","username": "root","password": "root","database": "pomelo"}
}
加载配置文件,用户通过loadConfig()
加载配置文件后,加载后文件中的参数将会直接挂载到app
对象上,可直接通过app
对象访问具体的配置参数。
$ vim game-server/app.js
const path = require('path');
//全局配置
app.configure('production|development', function(){//加载MySQL数据库app.loadConfig("mysql", path.join(app.getBase(), "config/mysql.json"));const host = app.get("mysql").host;//获取配置console.log("mysql config: host = %s",host);
});
用户可以使用loadConfig()
的调用加载任何JSON
格式的配置文件,用于其它的目的,并能通过app
进行访问。需要注意的是所有的JSON
配置文件中都需要指定具体的模式,也就是development
或production
。
/*** Load Configure json file to settings.** @param {String} key environment key* @param {String} val environment value* @return {Server|Mixed} for chaining, or the setting value* @memberOf Application*/
Application.loadConfig = function(key, val) {var env = this.get(Constants.RESERVED.ENV);val = require(val);if (val[env]) {val = val[env];}this.set(key, val);
};
set/get
上下文变量存取是指上下文对象app
提供了设置和获取应用变量的方法,签名为:
set 设置应用变量
app.set(name, value, [isAttach]);
参数 | 描述 |
---|---|
name | 变量名 |
value | 变量值 |
isAttach |
可选,默认为false,附加属性,若isAttach为true则将变量attach到app对象上作为属性。此后对此变量的访问,可直接通过app.name 。
|
/*** Assign `setting` to `val`, or return `setting`'s value.** Example:** app.set('key1', 'value1');* app.get('key1'); // 'value1'* app.key1; // undefined** app.set('key2', 'value2', true);* app.get('key2'); // 'value2'* app.key2; // 'value2'** @param {String} setting the setting of application* @param {String} val the setting's value* @param {Boolean} attach whether attach the settings to application* @return {Server|Mixed} for chaining, or the setting value* @memberOf Application*/
Application.set = function (setting, val, attach) {if (arguments.length === 1) {return this.settings[setting];}this.settings[setting] = val;if(attach) {this[setting] = val;}return this;
};
例如:
app.set("name", "project_name");
const name = app.get("name);//project_name
app.set("name", name, true);
const name = app.name;
get 获取应用变量
app.get(name);
/*** Get property from setting** @param {String} setting application setting* @return {String} val* @memberOf Application*/
Application.get = function (setting) {return this.settings[setting];
};
例如:获取项目根目录,即app.js
文件所在的目录。
const basepath = app.get("base");
// const basepath = app.getBase();
enable/disable
开发者可通过enable()/disable()方法来启用或禁用Pomelo框架的一些特性,并通过enabled()/disabled()方法来检查特性的可用状态。
例如:禁用及启用RPC调试日志并检查其状态
app.enabled("rpcDebugLog");//return true/false
app.disabled("rpcDebugLog");app.enable("rpcDebugLog");
app.disable("rpcDebugLog");
例如:启用systemMonitor以加载额外模块
app.enable("systemMonitor");
route
route主要负责请求路由信息的维护,路由计算,路由结果缓存等工作,并根据需要切换路由策略,更新路由信息等。
/*** Set the route function for the specified server type.** Examples:** app.route('area', routeFunc);** var routeFunc = function(session, msg, app, cb) {* // all request to area would be route to the first area server* var areas = app.getServersByType('area');* cb(null, areas[0].id);* };** @param {String} serverType server type string* @param {Function} routeFunc route function. routeFunc(session, msg, app, cb)* @return {Object} current application instance for chain invoking* @memberOf Application*/
Application.route = function(serverType, routeFunc) {var routes = this.get(Constants.KEYWORDS.ROUTE);if(!routes) {routes = {};this.set(Constants.KEYWORDS.ROUTE, routes);}routes[serverType] = routeFunc;return this;
};
用户可自定义不同服务器的不同路由规则,然后进行配置即可。在路由函数中,通过最后的回调函数中返回服务器的ID即可。
$ vim game-server/app.js
//聊天服务器配置
app.configure("production|development", "chat",function(){//路由配置app.route("chat", function(session, msg, app, cb){const servers = app.getServersByType("chat");if(!servers || servers.length===0){cb(new Error("can not find chat servers"));return;}const val = session.get("rid");if(!val){cb(new Error("session rid is not find"));return;}const index = Math.abs(crc.crc32(val)) % servers.length;const server = servers[index];cb(null, server.id);});//过滤配置app.filter(pomelo.timeout());
});
filter
实际应用中,往往需要在逻辑服务器处理请求之前对用户请求做一些前置处理,当请求被处理后又需要做一些善后处理,由于这是一种常见的情形。Pomelo对其进行了抽象,也就是filter。在Pomelo中filter分为before filter和after filter。在一个请求到达Handler被处理之前,可以经过多个before filter组成的filter链进行一些前置处理,比如对请求进行排队,超时处理。当请求被Handler处理完成后,又可以通过after filter链进行一些善后处理。这里需要注意的是在after filter中一般只做一些清理处理,而不应该再去修改到客户端的响应内容。因为此时,对客户端的响应内容已经发送给了客户端。
filter链
filter
分为before
和after
两类,每个filter
都可以注册多个形成一个filter
链,所有客户端请求都会经过filter
链进行处理。before filter
会对请求做一些前置处理,如检查当前玩家是否已经登录,打印统计日志等。after filter
是进行请求后置处理的地方,比如释放请求上下文的资源,记录请求总耗时等。after filter
中不应该再出现修改响应内容的代码,因为在进入after filter
前响应就已经被发送给客户端。
配置filter
当一个客户端请求到达服务器后,经过filter链和handler处理,最后生成响应返回给客户端。handler是业务逻辑实现的地方,filter则是执行业务前进行预处理和业务处理后清理的地方。为了开发者方便,系统内建提供了一些filter。比如serialFilter、timerFilter、timeOutFilter等,另外,用户可以根据应用的需要自定义filter。
app.filter(pomelo.filters.serial());
如果仅仅是before filter,那么调用app.before。
/*** Add before filter.** @param {Object|Function} bf before fileter, bf(msg, session, next)* @memberOf Application*/
Application.before = function (bf) {addFilter(this, Constants.KEYWORDS.BEFORE_FILTER, bf);
};
如果是after filter,则调用app.after。
/*** Add after filter.** @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`* @memberOf Application*/
Application.after = function (af) {addFilter(this, Constants.KEYWORDS.AFTER_FILTER, af);
};
如果即定义了before filter,又定义了after filter,可以使用app.filter调用。
/*** add a filter to before and after filter** @param {Object} filter provide before and after filter method.* A filter should have two methods: before and after.* @memberOf Application*/
Application.filter = function (filter) {this.before(filter);this.after(filter);
};
用户可以自定义filter,然后通过app.filter调用,将其配置进框架。
filter对象
filter是一个对象,定义filter大致代码如下:
let Filter = function(){};
/*** 前置过滤器* @param msg 用户请求原始内容或经前面filter链处理后的内容* @param session 若在后端服务器上则是BackendSession,若在前端服务器则是FrontendSession* @param next*/
Filter.prototype.before = function(msg, session, next){};
/*** 后置过滤器* @param err 错误信息* @param msg* @param session* @param resp 对客户端的响应内容* @param next*/
Filter.prototype.after = function(err, msg, session, resp, next){};
module.exports = function(){return new Filter();
};
服务器启动app.start()
当执行完用户编辑代码后,将会进入app.start()
调用,它首先会加载默认的组件,对于master服务器来说加载的默认组件时master组件和monitor组件。
/*** Start application. It would load the default components and start all the loaded components.** @param {Function} cb callback function* @memberOf Application*/Application.start = function(cb) {this.startTime = Date.now();if(this.state > STATE_INITED) {utils.invokeCallback(cb, new Error('application has already start.'));return;}var self = this;appUtil.startByType(self, function() {appUtil.loadDefaultComponents(self);var startUp = function() {appUtil.optComponents(self.loaded, Constants.RESERVED.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);}});};var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP];if(!!beforeFun) {beforeFun.call(null, self, startUp);} else {startUp();}});
};
master组件的启动过程
master组件的启动过程
- app.start()方法首先会加载默认组件,由于没有指定服务器类型,此时会默认为master服务器类型,并获取master服务器的配置、加载master组件。
$ vim game-server/config/master.json
{"development": {"id": "master-server-1", "host": "127.0.0.1", "port": 3005},"production": {"id": "master-server-1", "host": "127.0.0.1", "port": 3005}
}
由于Master组件是以工厂函数的方式导出的,因此会创建master组件,master组件的创建过程中会创建MasterConsole,MasterConsole会创建MasterAgent,MasterAgent会创建监听Socket用来监听应用服务器的监控和管理请求。
Pomelo启动活动图
转自Pomelo Application - 简书
Pomelo Application相关推荐
- (十一)nodejs循序渐进-高性能游戏服务器框架pomelo之启动流程和组件
游戏启动过程 启动入口 在使用pomelo进行游戏开发时,工程目录下的app.js是整个游戏服务器的启动运行入口.app.js中创建项目,进行默认配置并启动服务器的代码如下: var pomelo = ...
- Pomelo:网易开源基于 Node.js 的游戏服务端框架
Pomelo 是基于 Node.js 的高性能.分布式游戏服务器框架.它包括基础的开发框架和相关的扩展组件(库和工具包),可以帮助你省去游戏开发枯燥中的重复劳动和底层逻辑的开发.Pomelo 不但适用 ...
- pomelo + vscode + typescript搭建可约束可调试的游戏服务端框架
说在前面 pomelo: 它是网易开源的一套基于Node.js的游戏服务端框架,详情请戳这里关于pomelo的种种这里不详细说.点击链接查看详情.但是由于pomelo是js项目,使用起来的时候并不是很 ...
- pomelo服务器 性能,Pomelo游戏服务器端开发系列(1)-介绍
Pomelo框架总结 A fast,scalable,distributed game server framework for Node.js 联系我 Pomelo交流群 @老顽童-NextZeus ...
- Pomelo 使用教程
继"Hello World"之后,我们参照官方文档,以一个"Chat"为例进一步学习Pomelo的使用.在本文中,将会涵盖筛选器.路由及消息压缩.RPC调用.组 ...
- Pomelo框架总结
Pomelo框架总结 A fast,scalable,distributed game server framework for Node.js 联系我 Pomelo交流群 @老顽童-NextZeus ...
- pomelo源码解析--新建项目(cli工具: pomelo)
pomelo怎么新建项目 官方文档 1. 安装pomelo 2. 新建项目HelloWorld 我简单整理了下创建新项目关键步骤: 安装pomelo 方式一: $ npm install pomelo ...
- pomelo架构概览
pomelo架构概览 pomelo之所以简单易用.功能全面,并且具有高可扩展性.可伸缩性等特点,这与它的技术选型和方案设计是密不可分的.在研究大量游戏引擎设计思路基础上,结合以往游戏开发的经验,确定了 ...
- Pomelo App
应用程序配置,如何配置Pomelo框架? Pomelo可以配置各个组件的选项,加载配置文件,开启Pomelo的特性等,这些配置都是在game-server/app.js文件中进行的.实际上在Pomel ...
最新文章
- 9/6字节校招研发岗位笔试
- redirect-action
- 学习笔记-canny边缘检测
- c++gdal如何在大图像中截取小图像并获取其图像信息_【图像处理】OpenCV系列十 --- 边缘检测之Canny算子...
- xp电脑主题包_怎么让手机变电脑?一个APP让你的安卓手机变Windows电脑
- 【NLTK基础】一文轻松使用NLTK进行NLP任务(附视频)
- 机器学习概念篇:监督学习、过拟合,正则化,泛化能力等概念以及防止过拟合方法总结
- 象棋 计算机配置,象棋名手要什么配置的电脑运行最佳,用来弈天砍分
- 使用PYTHON采集船舶MMSI数据
- 电子面单打印通用解决方案(PHP代码示例)
- 后台任务列表 App 界面模糊处理
- 【原创】DDR3 SO-DIMM 内存条硬件知识
- 2007年大学生电子设计大赛国赛电源类题目设计报告
- 设计原则:里式替换原则(LSP)
- 【spring的使用方法】
- python 根据TIN查询点云坐标
- boost spirit 解析字符串 (一)
- (蓝桥杯第五届B组)史丰收速算 打印图形(代码填空)
- 达梦数据库亮相第七届中国国际国防电子展览会
- 学生成绩管理分析系统的设计与实现(论文+源码)_kaic