[egret+pomelo]实时游戏杂记(1)
资料
准备工作
1.下载并搭建pomelo项目
2.下载pomelo捡宝项目(github上下载的,最好是看一遍git上的教程,再进行搭建会比较顺利)
3.下载的捡宝项目[Treasures] 中有简略的项目教程,可以帮助我们快速搭建和熟悉捡宝项目。
开始创建Egret项目:
因个人比较熟悉egret引擎,在论坛中找到egret pomelo的第三方库
1.客户端代码:
使用egret wing 创建游戏项目,在项目src目录下,创建network文件夹,在文件夹下新建PomeloSocket类用来链接Pomelo服务端
module network {
/**
* 链接pomelo服务端
*/
export class PomeloSocket {
public constructor() {
}
private pomelo: Pomelo;
/**
* 当前正在操作的是服务端
*/
private currServer: network.PomeloService;
/**
* 服务端状态 是否开启
*/
private running: boolean = false;
init() {
if (this.pomelo == null) {
this.pomelo = new Pomelo();
this.pomelo.on('server_push_message', (msg) => {
var route = msg["route"];
//根据服务端返回派发事件
{
switch (route) {
case "addEntities":
Global.dispatchEvent(events.PomeloServerEvents.ADDENTITIES, msg);
break;
case "rankUpdate":
Global.dispatchEvent(events.PomeloServerEvents.RANKUPDATE, msg);
break;
case "onUserLeave":
Global.dispatchEvent(events.PomeloServerEvents.USERLEAVE, msg);
break;
case "removeEntities":
Global.dispatchEvent(events.PomeloServerEvents.REMOVEENTITIES, msg);
break;
case "onMove":
Global.dispatchEvent(events.PomeloServerEvents.ENTITYMOVE, msg);
break;
case "onChangeStage":
Global.dispatchEvent(events.PomeloServerEvents.STAGECHANGE, msg);
break;
default:
trace("收到新的需要处理的事件~~~~~~~~~~~~~~待处理信息为:");
trace(msg);
break;
}
}
});
this.pomelo.on('onKick', (msg) => {
trace("onKick");
});
this.pomelo.on('heartbeat_timeout', () => {
trace("heartbeat_timeout");
});
this.pomelo.on('close', (e: CloseEvent) => {
trace(e.currentTarget["url"] + "的链接被断开");
});
}
}
/**
* 打开服务端
* @param serverType:服务端类型
* @param host:ip
* @param port:端口
* @param callback:回调函数
* @param log:是否启用日志
*/
open(serverType: network.PomeloService, host: string, port: number, callback?: Function, log: boolean = true) {
this.pomelo.init({ host: host, port: port, log: log }, false, (succeedRes) => {
this.currServer = serverType;
this.running = true;
switch (serverType) {
case network.PomeloService.GATE:
Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_GATE_SUCCEED);
break;
case network.PomeloService.CONNECTION:
Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_CONNECT_SUCCEED);
break;
default:
trace("========================试图打开程序中未知服务器,请求被拒绝=========================================");
break;
}
}, (errRES) => {
switch (serverType) {
case network.PomeloService.GATE:
Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_GATE_ERROR);
break;
case network.PomeloService.CONNECTION:
Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_CONNECT_ERROR);
break;
default:
trace("========================试图打开程序中未知服务器,请求被拒绝=========================================");
break;
}
}, (closeRes) => {
trace("一个服务端关闭完成。");
}, null);
}
/**
* 发起请求
* @param route: 路由 (服务端处理函数)
* @param msg:内容
* @param callback:回调函数
* @param thisArg:参数
*/
request(route: string, msg: any, callback: Function, thisArg?: any): void {
this.pomelo.request(route, msg, (response) => {
callback.call(thisArg, response);
});
}
/**
* 通知
*/
notify(route: string, msg: any): void {
this.pomelo.notify(route, msg);
}
/**
* 关闭当前服务
*/
disconnect() {
this.pomelo.disconnect();
this.running = false;
Global.dispatchEvent(events.PomeloServerEvents.DISCONNECT_SUCCEED, { currServer: this.currServer });
}
/**
* 获取当前的服务端
*/
getCurrServer(): PomeloService {
return this.currServer;
}
/**
* 获取当前的服务端状态
*/
isRunning(): boolean {
return this.running;
}
}
}
在文件夹下新建PomeloService类用来链接Pomelo服务端
module network {
/**
* 服务端模块列表
*/
export class PomeloService {
public constructor() {
}
/**
* Gate模块
*/
public static GATE: string = "PomeloService_GATE";
/**
* Connect 模块操作
*/
public static CONNECTION: string = "PomeloService_CONNECTION";
}
}
在项目src目录下创建pomeloTest文件,链接pomelo相应的服务端
class PomeloTest {
private connectIp: string;
private connectPort: number;
public constructor() {
Global.addEventListener(events.PomeloServerEvents.CONNECTION_GATE_SUCCEED, this.onGateSucceed, this);
Global.addEventListener(events.PomeloServerEvents.CONNECTION_GATE_ERROR, this.onGateError, this);
Global.addEventListener(events.PomeloServerEvents.CONNECTION_CONNECT_SUCCEED, this.onConnectSucceed, this);
Global.addEventListener(events.PomeloServerEvents.CONNECTION_CONNECT_ERROR, this.onConnectError, this);
}
connectGate() {
config.Config.pomelo.init();
config.Config.pomelo.open(network.PomeloService.GATE, config.Config.gateServer.ip, config.Config.gateServer.port);
}
private onGateSucceed() {
Global.addEventListener(events.PomeloServerEvents.DISCONNECT_SUCCEED, this.onGateClosed, this);
config.Config.pomelo.request("gate.gateHandler.queryEntry", { uid: config.Config.player.name }, this.onGateMsg);
trace("Gate服务端链接成功");
}
private onGateError() {
trace("Gate服务端链接失败");
}
private onGateMsg(gate_data) {
this.connectIp = gate_data.host;
this.connectPort = gate_data.port;
config.Config.pomelo.disconnect();
trace("正在尝试链接connect服务端...");
config.Config.pomelo.open(network.PomeloService.CONNECTION, this.connectIp, this.connectPort);
}
private onGateClosed() {
trace("Gate服务端成功断开链接");
// trace("正在尝试链接connect服务端...");
// config.global.pomelo.open(network.PomeloService.CONNECTION, this.connectIp, this.connectPort);
}
private onConnectSucceed() {
trace("CONNECT服务端链接成功");
trace("开始注册服务端信息...");
config.Config.pomelo.request('connector.entryHandler.entry', { name: config.Config.player.name }, this.onEntryMsg);
}
private onConnectError() {
trace("CONNECT服务端链接失败...");
}
private onEntryMsg(entry_data) {
if (entry_data.code === 200) {
trace("注册信息成功");
trace("开始申请进入游戏...");
config.Config.pomelo.request('area.playerHandler.enterScene', { name: config.Config.player.name, playerId: entry_data.playerId }, (respose) => {
Global.dispatchEvent(events.PomeloServerEvents.MAPMSG, respose);
trace("进入游戏成功");
trace("开始解析地图信息");
});
} else {
trace("注册服务端信息出现问题,请检查提交信息");
}
}
move(x: number, y: number, targetId: string) {
config.Config.pomelo.notify('area.playerHandler.move', { targetPos: { x: x, y: y }, target: targetId });
}
changeStage(s: string) {
config.Config.pomelo.notify('area.playerHandler.changeStage', { S: s });
}
}
2.服务端代码
以上步骤都是准备工作,各语言间的链接方式和代码都不相同,如果没有使用egret可以使用pomelo项目中自带的web-server项目就可以轻松搭建起来的,接下来,就是服务端中的代码说明,因为本人的代码写的并不是很好,所以既然想做个好点的游戏,一步一步剖析pomelo的运行方式是很重要的一步。
2.1代码执行流程
在game-server文件夹下 直接使用pomelo命令启动app.js
pomelo start

出现这样的界面,就证明pomelo服务端启动成功了。那首先的一步 就是查看app文件中的代码
var bearcat = require('bearcat');
var pomelo = require('pomelo');
/**
* Init app for client.
*/
var app = pomelo.createApp();
当代码执行到 var app = pomelo.createApp(); 这句时,将执行game-server/node_modules/pomelo/pomelo.js 文件中的 createApp方法
var application = require('./application');
/**
* 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;
};
这个方法中一共四行执行代码,第一行是引用了pomelo.js同级目录下的application.js文件,对pomelo文件中的application对象进行初始化,并将初始化的对方返回给调用该方法的app.js中的app对象。下面我们看一下,这个app.init(opts);这句话具体做了些什么呢? 我们进入application文件看一下。

前面标红的地方,是对当前application文件中的信息进行一个初始化,因为createApp()调用时并未对其传递相关的opts参数,所以,这里涉及到opts变量相关的应该是“undefined”。
appUtil.defaultConfiguration(this);是做什么的呢?我们转到【 var appUtil = require('./util/appUtil');】 game-server/node_modules/pomelo/util/appUtil.js文件查找defaultConfiguration方法。

setupEnv (环境配置)
var setupEnv = function (app, args) {
app.set(Constants.RESERVED.ENV, args.env || process.env.NODE_ENV || Constants.RESERVED.ENV_DEV, true);
};
app.set方法:(设置配置文件,并返回设置的值)
/**
* 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;
};
loadMaster(加载master json文件)
var loadMaster = function (app) {
app.loadConfigBaseApp(Constants.RESERVED.MASTER, Constants.FILEPATH.MASTER);
app.master = app.get(Constants.RESERVED.MASTER);
};
app.loadConfigBaseApp方法:(递归方式 加载json配置文件)
/**
* Load Configure json file to settings.(support different enviroment directory & compatible for old path)
*
* @param {String} key environment key
* @param {String} val environment value
* @param {Boolean} reload whether reload after change default false
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
Application.loadConfigBaseApp = function (key, val, reload) {
var self = this;
var env = this.get(Constants.RESERVED.ENV);
var originPath = path.join(Application.getBase(), val);
var presentPath = path.join(Application.getBase(), Constants.FILEPATH.CONFIG_DIR, env, path.basename(val));
var realPath;
if(fs.existsSync(originPath)) {
realPath = originPath;
var file = require(originPath);
if (file[env]) {
file = file[env];
}
this.set(key, file);
} else if(fs.existsSync(presentPath)) {
realPath = presentPath;
var pfile = require(presentPath);
this.set(key, pfile);
} else {
logger.error('invalid configuration with file path: %s', key);
} if(!!realPath && !!reload) {
fs.watch(realPath, function (event, filename) {
if(event === 'change') {
delete require.cache[require.resolve(realPath)];
self.loadConfigBaseApp(key, val);
}
});
}
};
master 的json文件加载完成了,下一步就是加载server的json文件
/**
* Load server info from config/servers.json.
*/
var loadServers = function (app) {
app.loadConfigBaseApp(Constants.RESERVED.SERVERS, Constants.FILEPATH.SERVER);
var servers = app.get(Constants.RESERVED.SERVERS);
var serverMap = {}, slist, i, l, server;
for (var serverType in servers) {
slist = servers[serverType];
for (i = 0, l = slist.length; i < l; i++) {
server = slist[i];
server.serverType = serverType;
if (server[Constants.RESERVED.CLUSTER_COUNT]) {
utils.loadCluster(app, server, serverMap);
continue;
}
serverMap[server.id] = server;
if (server.wsPort) {
logger.warn('wsPort is deprecated, use clientPort in frontend server instead, server: %j', server);
}
}
}
app.set(Constants.KEYWORDS.SERVER_MAP, serverMap);
};
首先加载server的json文件并存储于app中,遍历读取到的servers的serverType,通过servers[serverType]可获取到对应的服务端配置组,使用utils.loadCluster(app, server, serverMap);方法操作, game-server/node_modules/pomelo/util/util.js,下面来看一下loadCluster方法是来做什么的。
/**
* Load cluster server.
*
*/
utils.loadCluster = function(app, server, serverMap) {
var increaseFields = {};
var host = server.host;
var count = parseInt(server[Constants.RESERVED.CLUSTER_COUNT]);
var seq = app.clusterSeq[server.serverType];
if(!seq) {
seq = 0;
app.clusterSeq[server.serverType] = count;
} else {
app.clusterSeq[server.serverType] = seq + count;
} for(var key in server) {
var value = server[key].toString();
if(value.indexOf(Constants.RESERVED.CLUSTER_SIGNAL) > 0) {
var base = server[key].slice(0, -2);
increaseFields[key] = base;
}
} var clone = function(src) {
var rs = {};
for(var key in src) {
rs[key] = src[key];
}
return rs;
};
for(var i=0, l=seq; i<count; i++,l++) {
var cserver = clone(server);
cserver.id = Constants.RESERVED.CLUSTER_PREFIX + server.serverType + '-' + l;
for(var k in increaseFields) {
var v = parseInt(increaseFields[k]);
cserver[k] = v + i;
}
serverMap[cserver.id] = cserver;
}
};
这个方法可以看出,是用来做集群间的负载均衡,将设置app中的clusterSeq 属性值,以用来存储集群的ID。
processArgs(创建进程启动服务端)
/**
* Process server start command
*/
var processArgs = function (app, args) {
var serverType = args.serverType || Constants.RESERVED.MASTER;
var serverId = args.id || app.getMaster().id;
var mode = args.mode || Constants.RESERVED.CLUSTER;
var masterha = args.masterha || 'false';
var type = args.type || Constants.RESERVED.ALL;
var startId = args.startId; app.set(Constants.RESERVED.MAIN, args.main, true);
app.set(Constants.RESERVED.SERVER_TYPE, serverType, true);
app.set(Constants.RESERVED.SERVER_ID, serverId, true);
app.set(Constants.RESERVED.MODE, mode, true);
app.set(Constants.RESERVED.TYPE, type, true);
if (!!startId) {
app.set(Constants.RESERVED.STARTID, startId, true);
} if (masterha === 'true') {
app.master = args;
app.set(Constants.RESERVED.CURRENT_SERVER, args, true);
} else if (serverType !== Constants.RESERVED.MASTER) {
app.set(Constants.RESERVED.CURRENT_SERVER, args, true);
} else {
app.set(Constants.RESERVED.CURRENT_SERVER, app.getMaster(), true);
}
};
这个方法设置了app的一些属性参数值,后两步的日志文件和生命周期,放到后面的章节再研究,现在app的信息已经完善,至此,appUtil.appdefaultConfiguration方法执行完成,Pomelo.createApp执行完成,并将app返回给app.js文件中的app对象,思路回到app.js中,代码继续向下走
var bearcat = require('bearcat');
var pomelo = require('pomelo');
/**
* Init app for client.
*/
var app = pomelo.createApp();
var Configure = function() {
app.set('name', 'treasures');
app.configure('production|development', 'gate', function() {
app.set('connectorConfig', {
connector: pomelo.connectors.hybridconnector
});
});
app.configure('production|development', 'connector', function() {
app.set('connectorConfig', {
connector: pomelo.connectors.hybridconnector,
heartbeat: 100,
useDict: true,
useProtobuf: true
});
});
app.configure('production|development', 'area', function() {
var areaId = app.get('curServer').areaId;
if (!areaId || areaId < 0) {
throw new Error('load area config failed');
}
var areaService = bearcat.getBean('areaService');
var dataApiUtil = bearcat.getBean('dataApiUtil');
areaService.init(dataApiUtil.area().findById(areaId));
});
}
app赋值完成之后,声明了Configure对象,这里貌似是接收消息使用的,下面来去到application.configure。
function load(path, name) {
if (name) {
return require(path + name);
}
return require(path);
}
/**
* connectors
*/
Pomelo.connectors = {};
Pomelo.connectors.__defineGetter__('sioconnector', load.bind(null, './connectors/sioconnector'));
Pomelo.connectors.__defineGetter__('hybridconnector', load.bind(null, './connectors/hybridconnector'));
Pomelo.connectors.__defineGetter__('udpconnector', load.bind(null, './connectors/udpconnector'));
Pomelo.connectors.__defineGetter__('mqttconnector', load.bind(null, './connectors/mqttconnector'));
Pomelo.connectors.__defineGetter__('hybridconnector', load.bind(null, './connectors/hybridconnector')); 将 game-server/node_modules/pomelo/connectors/hybridconnector.js的引用赋值给app中的connectorConfig属性设置,这个connector可以看做是一个链接的控制器,后续的操作将围绕着这个connector对象来开展。
至此,app的创建准备工作便完成了。
下面是重要的一步,程序开始,通过start方法启动服务端

关于更多请关注 bearcat 的介绍
其它
从代码中可以看出这个app已经启动完成,在这个期间有还有一个在application文件中的对象Constants,constants文件是记录程序中的一些基础的配置。
module.exports = {
KEYWORDS: {
BEFORE_FILTER: '__befores__',
AFTER_FILTER: '__afters__',
GLOBAL_BEFORE_FILTER: '__globalBefores__',
GLOBAL_AFTER_FILTER: '__globalAfters__',
ROUTE: '__routes__',
BEFORE_STOP_HOOK: '__beforeStopHook__',
MODULE: '__modules__',
SERVER_MAP: '__serverMap__',
RPC_BEFORE_FILTER: '__rpcBefores__',
RPC_AFTER_FILTER: '__rpcAfters__',
MASTER_WATCHER: '__masterwatcher__',
MONITOR_WATCHER: '__monitorwatcher__'
},
FILEPATH: {
MASTER: '/config/master.json',
SERVER: '/config/servers.json',
CRON: '/config/crons.json',
LOG: '/config/log4js.json',
SERVER_PROTOS: '/config/serverProtos.json',
CLIENT_PROTOS: '/config/clientProtos.json',
MASTER_HA: '/config/masterha.json',
LIFECYCLE: '/lifecycle.js',
SERVER_DIR: '/app/servers/',
CONFIG_DIR: '/config'
},
DIR: {
HANDLER: 'handler',
REMOTE: 'remote',
CRON: 'cron',
LOG: 'logs',
SCRIPT: 'scripts',
EVENT: 'events',
COMPONENT: 'components'
},
RESERVED: {
BASE: 'base',
MAIN: 'main',
MASTER: 'master',
SERVERS: 'servers',
ENV: 'env',
CPU: 'cpu',
ENV_DEV: 'development',
ENV_PRO: 'production',
ALL: 'all',
SERVER_TYPE: 'serverType',
SERVER_ID: 'serverId',
CURRENT_SERVER: 'curServer',
MODE: 'mode',
TYPE: 'type',
CLUSTER: 'clusters',
STAND_ALONE: 'stand-alone',
START: 'start',
AFTER_START: 'afterStart',
CRONS: 'crons',
ERROR_HANDLER: 'errorHandler',
GLOBAL_ERROR_HANDLER: 'globalErrorHandler',
AUTO_RESTART: 'auto-restart',
RESTART_FORCE: 'restart-force',
CLUSTER_COUNT: 'clusterCount',
CLUSTER_PREFIX: 'cluster-server-',
CLUSTER_SIGNAL: '++',
RPC_ERROR_HANDLER: 'rpcErrorHandler',
SERVER: 'server',
CLIENT: 'client',
STARTID: 'startId',
STOP_SERVERS: 'stop_servers',
SSH_CONFIG_PARAMS: 'ssh_config_params'
},
COMMAND: {
TASKSET: 'taskset',
KILL: 'kill',
TASKKILL: 'taskkill',
SSH: 'ssh'
},
PLATFORM: {
WIN: 'win32',
LINUX: 'linux'
},
LIFECYCLE: {
BEFORE_STARTUP: 'beforeStartup',
BEFORE_SHUTDOWN: 'beforeShutdown',
AFTER_STARTUP: 'afterStartup',
AFTER_STARTALL: 'afterStartAll'
},
SIGNAL: {
FAIL: 0,
OK: 1
},
TIME: {
TIME_WAIT_STOP: 3 * 1000,
TIME_WAIT_KILL: 5 * 1000,
TIME_WAIT_RESTART: 5 * 1000,
TIME_WAIT_COUNTDOWN: 10 * 1000,
TIME_WAIT_MASTER_KILL: 2 * 60 * 1000,
TIME_WAIT_MONITOR_KILL: 2 * 1000,
TIME_WAIT_PING: 30 * 1000,
TIME_WAIT_MAX_PING: 5 * 60 * 1000,
DEFAULT_UDP_HEARTBEAT_TIME: 20 * 1000,
DEFAULT_UDP_HEARTBEAT_TIMEOUT: 100 * 1000,
DEFAULT_MQTT_HEARTBEAT_TIMEOUT: 90 * 1000
}
};
由于基础太差需要好好吸收一下,本次就学到这,下章继续~
[egret+pomelo]实时游戏杂记(1)的更多相关文章
- [egret+pomelo]实时游戏杂记(3)
[egret+pomelo]学习笔记(1) [egret+pomelo]学习笔记(2) [egret+pomelo]学习笔记(3) 服务端的请求流程走完了一遍,下面就该看一下,在目前的服务端中,各服务 ...
- [egret+pomelo]实时游戏杂记(2)
[egret+pomelo]学习笔记(1) [egret+pomelo]学习笔记(2) [egret+pomelo]学习笔记(3) pomelo pomelo服务端介绍(game-server/con ...
- [egret+pomelo]实时游戏杂记(4)
了解了前后端的通信,下面就可以开始自己的业务逻辑了,首先玩家输入名称,选择角色后进入游戏世界. 服务端的demo中已经提供了一些简单的角色信息和属性,数据地址位于 game-server/config ...
- [egret+pomelo]实时对战游戏杂记(5)
之前大体了解了pomelo服务端的运行的大体运行流程,下面详细的学习一下在服务端比较重要的一个容器模块bearcat,在bearcat的wiki中我们可以对其有个大概的了解,在服务端示例的代码中也大量 ...
- Egret 之 消除游戏 开发 PART 6 Egret elimination game development PART 6
Egret 之 消除游戏 开发 PART 6 Egret elimination game development PART 6 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱: ...
- Pomelo分布式游戏服务器框架
Pomelo介绍&入门 目录 前言&介绍 安装Pomelo 创建项目并启动 创建项目 项目结构说明 启动 测试连接 聊天服务器 新建gate和chat服务器 配置master.json ...
- 为什么MOBA和吃鸡类游戏不推荐用tcp协议 延迟不利于实时游戏
http://news.gamedog.cn/a/20171221/2287418.html 我们知道,不同类型的游戏因为玩法.竞技程度不一样,采用的同步算法不一样,对网络延迟的要求也不一样.例如,M ...
- 微信小游戏 查看egret的小游戏支持库版本
在开发者工具 console输入egret.wxgame
- egret之消除游戏开发
1.地图 (1)地图形状不同,尺寸不变 (2)背景图变化 2.步数 (1)不同关卡步数不同 (2)步数为01,游戏失败 3.道具 4.消除 (1)>=3可消除 (2)不可消除时,自动打乱 5.数 ...
随机推荐
- sencha toucha获取 constructor中的数据
config:{ tmp:null }, constructor : function(conf) { this.config.tmp=conf; } 添加配置属性,然后直接用 this.config ...
- 2017.2.20 activiti实战--第一章--认识Activiti
学习资料:<Activiti实战> 第一章 认识Activiti 内容概览:讲解activiti的特点.接口概览.架构等基本信息. 1.3 Activiti的特点 1.使用mybatis ...
- 手动脱Mole Box壳实战总结
作者:Fly2015 这个程序是吾爱破解脱壳练习第8期的加壳程序,该程序的壳是MoleBox V2.6.5壳,这些都是广告,能够直接无视了.前面的博客手动脱Mole Box V2.6.5壳实战中已经给 ...
- 《学习bash》笔记--调试shell程序
在shell中,最简单的调试助手时输出语句echo,能够通过把很多echo语句放到代码中进行调试,但必须花费足够的时间以定位 要查看的信息.可能必须通过很多的输出才干发现要查找的信息. 1.set选项 ...
- Unique Binary Search Trees I&II——给定n有多少种BST可能、DP
Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For examp ...
- SharePoint 的PowerShell命令之获取所有网站模版
Get-SPWebTemplate | select Name, Title
- iOS 振动反馈
代码地址如下:http://www.demodashi.com/demo/12461.html 1. 常用场景 继 iPhone7/7P 实体 home 键出现后,home 键再也无法通过真实的物理按 ...
- GitFlow工作流常用操作流程
1. 主要分支介绍 1.1 master分支 主分支,产品的功能全部实现后,最终在master分支对外发布. 1.2 develop分支 开发分支,基于master分支克隆,产品的编码工作在此分支进行 ...
- JAVA启动参数整理
http://blog.csdn.net/turkeyzhou/article/details/7619472 java启动参数共分为三类: 其一是标准参数(-),所有的JVM实现都必须实现这些参数的 ...
- 【转载】ORM的概念, ORM到底是什么
一.ORM简介 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.简单的说,ORM是通过使 ...