前言&介绍

  Pomelo:一个快速、可扩展、Node.js分布式游戏服务器框架

  从三四年前接触Node.js开始就接触到了Pomelo,从Pomelo最初的版本到现在,总的来说网易出品还算不错,但是发展不算快;用它做过一些项目和小游戏表现还不错。

  用它的主要好处:

  1. 入门简单,有比较丰富的文档和示例(虽然现在看版本也比较老了,但是入门没什么问题)

  2.分布式多进程且扩展简单(单进程多线程,每个服务器都是一个Node进程,通过配置文件就可以管理集群)

  3.可以不去关注底层和网络相关逻辑,聚焦业务逻辑的处理,对于有Web服务器开发经验却没有游戏服务器开发经验来说还是比较友好的

  4.提供了很多工具和客户端支持(像IOS、Android & Java、Javascript、C、Cocos2d-x、U3D等)

  ......  

  入门参考链接

  https://github.com/NetEase/pomelo/wiki/Home-in-Chinese

  其它链接:  

  https://github.com/NetEase/pomelo

  https://www.npmjs.com/package/pomelo

安装Pomelo

  安装要求  

Windows下安装要求环境

Python (2.5 < 版本 < 3)

VC++编译器

PS:  Windows新环境自已检查一下,我本机环境已经装好python2.7,Visaul Studio也安装了所以也有VC++编译器

  其它操作系统应该问题不大

官方安装介绍文档:https://github.com/NetEase/pomelo/wiki/%E5%AE%89%E8%A3%85pomelo

   全局安装Pomelo

npm install pomelo -g

  安装成功后如下图,可以看到现在最新版本为2.2.5

 说明:Pomelo光是安装可能出现各种失败

    1. 回头去检查一下,Python和VC++编辑器是否有问题

    2.如果以前全局安装过Pomelo,最好删除掉 “C:\Users\当前用户\AppData\Roaming\npm\node_modules”目录下Pomelo文件夹和“C:\Users\当前用户\AppData\Roaming\npm-cache”目录下Pomelo开头的文件夹

    3.如果并不报错,npm卡住不动,多数是网络原因,重复多安几次;或者打开FQ工具试试;也可以用淘宝镜像 cnpm 安装

创建项目并启动

安装好pomelo之后,开始创建项目并安装依赖项

pomelo init 项目名

 执行创建项目命令后,出现如下图选择项(Please select underly connector, 1 for websocket(native socket), 2 for socket.io, 3 for wss, 4 for socket.io(wss), 5 for udp, 6 for mqtt: [1]

这是让你选择connector的协议,除了5 for udp,其它都是长连接,我们接下来选择 2 for socket.io

在上图cmd中输入2,并回车,选择socket.io继续安装

这里connector协议可以通过app.js配置进行修改// app configuration

app.configure('production|development', 'connector', function(){
app.set('connectorConfig',
{
connector : pomelo.connectors.sioconnector,
    ...

});
});

成功后,转到项目根目录,执行安装项目执行 npm-install.bat 依赖项 (其它平台执行npm-install.sh)

cd 项目目录
npm-install.bat

项目创建完成后,目录如下图

项目结构说明

  game-server :  游戏服务器,所有游戏服务器功能和逻辑都在此目录下

    game-server/app.js:入口文件

    game-server/app: 存放游戏逻辑和功能相关代码都这个子目录下,servers目录下可以新建多个目录来创建不同类型的服务器,在pomelo中,使用路径来区分服务器类型

    game-server/config:存放游戏服务器配置文件目录,像日志、服务器、数据库等几乎所有配置文件都可以存放到此目录下

    game-server/logs:日志目录,存放游戏服务器所有日志文件

  web-server:  web服务器(如果你是个H5游戏,这里就是Web客户端,如果是IOS、Andriod客户端,这目录就没什么用)

  shared:公共代码存放处,这里要以放一些共用代码

所有依赖项安装成功后,开始启动项目

 启动game-server

cd game-server
pomelo start

启动命令执行成功后,出现如下图错误提示

[2017-11-23 11:54:42.226] [ERROR] console - Option path is not valid. Please refer to the README.
[2017-11-23 11:54:42.226] [ERROR] console - Option close timeout is not valid. Please refer to the README.
[2017-11-23 11:54:42.226] [ERROR] console - Option heartbeats is not valid. Please refer to the README.
[2017-11-23 11:54:42.226] [ERROR] console - Option log level is not valid. Please refer to the README.

  问题原因和解决方式

原因:新版的socket.io用法不正确的导致的,官方早已修复,就是没有publish到npm包中

修复方式:把node_modules目录下的pomelo中sioconnector.js(../game-server/node_modules/pomelo/lib/connectors/sioconnector.js)

替换为 https://github.com/NetEase/pomelo/blob/master/lib/connectors/sioconnector.js

 替换后再启动game-server,就没有这些错误提示了^_^!

 测试连接

1.启动web-server

cd web-server
node app

启动后如下图

会发些有一些提示,这是express写法问题,可以打开web-server根目录下app.js,按如下修改

//var app = express.createServer();  注释掉这一行代码,替换为下面这一行代码
var app = express();

再启动时无express用法提示^_^!

2.打开http://localhost:3001

 如上图,点击“Test Game Server”按钮,提示“game server is ok.”,则此项目game-server可用^_^!

聊天服务器

上面大体了解了pomelo,要入门还是以一个聊天服务器为入门示例最好,其它逻辑相对简单,入门学习不会因其它游戏逻辑影响。

官方有个非常好的示例:https://github.com/NetEase/chatofpomelo  官方也有很多说明

网上也有很多文章分析讲解这项目,我就不完全解释些项目了,接下来我就在上面新建的好的“PomeloDemo”的基础上改成一个聊天服务器

1.新建gate和chat服务器

在app/servers目录下新建gate和chat服务器,新建好后目录如下

gate服务器:

在一般情况下用户量一台机器就可以支撑,但用户量多了就得扩充服务器,gate服务器的作用就相当于前端负载均衡服务器;

客户端向gate服务器发出请求,gate服务器会给客户端分配一个connector服务器;

分配策略是根据客户端的某一个key做hash得到connector的id,这样就可以实现各个connector服务器的负载均衡。这个一会儿会实现

 connector服务器: 

接受客户端请求,并将其路由到chat服务器,以及维护客户端的链接;

同时,接收客户端对后端服务器的请求,按照用户配置的路由策略,将请求路由给具体的后端服务器。当后端服务器处理完请求或者需要给客户端推送消息的时候,connector服务器同样会扮演一个中间角色,完成对客户端的消息发送;

connector服务器会同时拥有clientPort和port,其中clientPort用来监听客户端的连接,port端口用来给后端提供服务;

chat服务器:

handler和remote决定了服务器的行为;

handler接收用户发送过来的send请求,remote由connector RPC发起远程调用时调用;

在remote里由于涉及到用户的加入和退出,所以会有对channel的操作。

其实也可以提前了解一些Pomelo中的术语,不分别解释,可以提前看看:https://github.com/NetEase/pomelo/wiki/%E6%9C%AF%E8%AF%AD%E8%A7%A3%E9%87%8A

2.配置master.json

{
"development": {
"id": "master-server-1", "host": "127.0.0.1", "port": 15005
},
"production": {
"id": "master-server-1", "host": "127.0.0.1", "port": 15005
}
}

3.配置servers.json

打开config目录下servers.json文件,配置好各种 type 的服务器,配置如下

{
"development":{
"connector":[
{"id":"connector-server-1", "host":"127.0.0.1", "port":14050, "clientPort": 13050, "frontend": true},
{"id":"connector-server-2", "host":"127.0.0.1", "port":14051, "clientPort": 13051, "frontend": true},
{"id":"connector-server-3", "host":"127.0.0.1", "port":14052, "clientPort": 13052, "frontend": true}
],
"chat":[
{"id":"chat-server-1", "host":"127.0.0.1", "port":16050},
{"id":"chat-server-2", "host":"127.0.0.1", "port":16051},
{"id":"chat-server-3", "host":"127.0.0.1", "port":16052}
],
"gate":[
{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 15014, "frontend": true}
]
},
"production":{
"connector":[
{"id":"connector-server-1", "host":"127.0.0.1", "port":14050, "clientPort": 13050, "frontend": true},
{"id":"connector-server-2", "host":"127.0.0.1", "port":14051, "clientPort": 13051, "frontend": true},
{"id":"connector-server-3", "host":"127.0.0.1", "port":14052, "clientPort": 13052, "frontend": true}
],
"chat":[
{"id":"chat-server-1", "host":"127.0.0.1", "port":16050},
{"id":"chat-server-2", "host":"127.0.0.1", "port":16051},
{"id":"chat-server-3", "host":"127.0.0.1", "port":16052}
],
"gate":[
{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 15014, "frontend": true}
]
}
}

解释一下配置中的各字段:

id:   字符串类型的应用服务器ID

host:应用服务器的IP或者域名

port:RPC请求监听的端口

clientPort: 前端服务器的客户端请求的监听端口

frontend:bool类型,是否是前端服务器,默认: false

可选参数:

max-connections:前端服务器最大客户连接数

args: node/v8配置,如配置为"args": "--debug=5858 "这样就可以启用项目调试(没用过,临时问了一下谷歌,看别人是这么解释的^_^!)

4.配置adminServer.json

 打开config目录下adminServer.json文件,配置好各种 type 的服务器,配置如下

[{
"type": "connector",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
}, {
"type": "chat",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
},{
"type": "gate",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
}]

它有什么作用,可以看一下以下2个链接

http://blog.csdn.net/nynyvkhhiiii/article/details/49249915

https://github.com/NetEase/pomelo-admin#server-master-auth

5.解决服务器分配问题

从上面的servers.json配置的修改可以看出与最开始创建出来的项目一个服务器相比,connector和chat我都配置了三个服务器

这就要解决客户端请求服务器分配问题

解决思路:用户访问gate服务器,使用用户的uid的crc32的校验码与connector服务器的个数取余,从而得到一个connector服务器,把这个connector服务器分配给请求用户

在app目录下新建util目录,目录下新建“dispatcher.js”和 “routeUtil.js”文件,处理此服务器分配逻辑

dispatcher.js

var crc = require('crc');

module.exports.dispatch = function(uid, connectors) {
var index = Math.abs(crc.crc32(uid)) % connectors.length;
return connectors[index];
};

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);
};

准备好这些文件后,在game-server服务器入口文件app.js中添加配配置

var pomelo = require('pomelo');
var routeUtil = require('./app/util/routeUtil'); /**
* Init app for client.
*/
var app = pomelo.createApp();
app.set('name', 'PomeloDemo'); // app configuration
// app.configure('production|development', 'connector', function(){
app.configure('production|development', function(){
// route configures
app.route('chat', routeUtil.chat);
app.set('connectorConfig',
{
connector : pomelo.connectors.sioconnector,
// 'websocket', 'polling-xhr', 'polling-jsonp', 'polling'
transports : ['websocket', 'polling'],
heartbeats : true,
closeTimeout : 60 * 1000,
heartbeatTimeout : 60 * 1000,
heartbeatInterval : 25 * 1000
});
// filter configures
app.filter(pomelo.timeout());
}); // start app
app.start(); process.on('uncaughtException', function (err) {
console.error(' Caught exception: ' + err.stack);
});
  app.filter(pomelo.timeout());
  过滤器,pomelo内置了一些过滤器,可以自行去了解一下,也可以根据自已的需求去自定义!

注意:

app.configure('production|development', 'connector', function(){

修改为

app.configure('production|development',  function(){

这个如果不修改,在启动调用时会遇到 engine.io 中报错  TypeError: Cannot read property  'indexOf' of undefined  at Server.verify !

6.实现 gate.gateHandler.queryEntry

作用:用户连接gate服务器,返回分配的connector

在gate目录下handler下新建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;
}
// get all connectors
var connectors = this.app.getServersByType('connector');
if(!connectors || connectors.length === 0) {
next(null, {
code: 500
});
return;
}
// select connector
var res = dispatcher.dispatch(uid, connectors);
next(null, {
code: 200,
host: res.host,
port: res.clientPort
});
};

7.实现chat服务器chatRemote.js 

chat服务器会接受connector的远程调用,完成channel维护中的用户的加入以及离开

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();
};

可以看到上面代码中的add和kick分别对应着加入和离开channel

8.实现chat服务器chatHandler.js

chat服务器执行聊天逻辑,维护channel信息,一个房间就是一个channel,一个channel里有多个用户,当有用户发起聊天的时候,就会将其内容广播到整个channel。

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 = {
route: 'onChat',
msg: msg.content,
from: username,
target: msg.target
};
channel = channelService.getChannel(rid, false); //the target is all users
if(msg.target == '*') {
channel.pushMessage(param);
}
//the target is specific user
else {
var tuid = msg.target + '*' + rid;
var tsid = channel.getMember(tuid)['sid'];
channelService.pushMessageByUids(param, [{
uid: tuid,
sid: tsid
}]);
}
next(null, {
route: msg.route
});
};

  这里面是发送消息(给房间内所有人和指定用户)

9.实现connector中entryHandler.js

主要完成接受客户端的请求,维护与客户端的连接,路由客户端的请求到chat服务器;

entryHandler.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
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)); //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
*
*/
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);
};

  这里完成的主要就是RPC远程调用chat服务器chatRemote中的实现

10.运行

到此这个聊天服务器实现就完成, 打开命令行工具,执行没有错误信息,基本就成功了!

cd game-server目录
pomelo start

编写web聊天客户端测试

我就在web-server目录中写了个测试客户端

把结构改了一下,换成了ejs模版,代码如下

routes中index.js文件代码

var express = require('express');
var router = express.Router(); router.get('/', function (req, res, next) {
res.render('index', { title: 'Nodejs学习笔记(十六)--- Pomelo介绍&入门' });
}); module.exports = router;

views中index.ejs文件代码

<html>
<head><title><%= title %></title></head>
<body>
<div id="tipMsg" style="color:red; height:30px;"></div>
<div id="pnlLogin">
<h1>1.登录(连接Gate服务器)</h1>
用户名:<input id="txtUserName" type="text" maxlength="20" ></br>
房间号:<input id="txtRoomId" type="text" maxlength="8" >
<input id="btnLogin" type="button" value="点击登录" />
<br/>
</div>
<div id="pnlChat" style="display:none;">
<h1>2.聊天室</h1>
用户名:<span id="spUserName" style="color:blue;padding-right:50px;"></span>
房间号:<span id="spRoomId" style="color:blue;padding-right:50px;"></span>
<div id="txtMessage" style="width:800px; height:400px;border:1px solid #000; overflow-y:auto; overflow-x:hidden; "></div>
<br/>
发送给:<select id="selUserList" style="width:200px;">
<option value="*">所有人</option>
</select>
<br/>
<br/>
<textarea id="txtSendMessage" type="text" style="width:690px;height:80px; overflow:auto;float:left;" ></textarea>
<input id="btnSend" type="button" value="发送" style="margin-left:10px; height:80px; width:100px;float:left;" />
</div>
</body>
</html>
<script src="js/socket.io.js"></script>
<script src="js/pomeloclient.js"></script>
<script src="js/jquery-1.11.2.min.js"></script>
<script type="text/javascript"> $(function(){ var rid = '';
var uname = ''; //监听"onAdd", 当有新用户加入时触发
pomelo.on('onAdd', function(data) {
var user = data.user;
$('#txtMessage').append('<span style="color:green;">[上线提醒]:欢迎 ' + user + ' 加入聊天室<span><br/>'); //添加到用户列表
$('#selUserList').append('<option value="' + user + '">' + user + '</option>');
}); //监听"onLeave", 当有用户离开聊天室时触发
pomelo.on('onLeave', function(data) {
var user = data.user;
$('#txtMessage').append('<span style="color:green;">[离线提醒]: ' + user + ' 离开聊天室<span><br/>'); //从用户列表移除
$('#selUserList option[value="' + user + '"]').remove();
}); // 监听"onChat", 接收消息
pomelo.on('onChat', function(data) {
var from = data.from,
target = data.target,
msg = data.msg; if(msg === null) return; var name = (target == '*' ? '所有人' : target);
var time = getNowFormatDate(); $("#txtMessage").append('<span style="color:blue;">[' + time +'][' + from + '] 对 [' + name + '] 说: ' + msg + '<span><br/>');
}); //当从聊天断开时
pomelo.on('disconnect', function(reason) {
$('#pnlLogin').show();
$('#pnlChat').hide();
}); //登录
$('#btnLogin').on('click', function(){
var userNameReg = /^[a-zA-Z0-9]+$/,
roomIdReg = /^[0-9]+$/; uname = $.trim($('#txtUserName').val()),
rid = $.trim($('#txtRoomId').val()); if(uname.length == 0){
alert('请输入登录名');
return false;
} if(!userNameReg.test(uname)) {
alert('登录名只能由字母或数字组成');
return false;
} if(rid.length == 0){
alert('请输入房间号');
return false;
} if(!roomIdReg.test(rid)) {
alert('房间号只能是数字');
return false;
} queryEntry(uname, function(host, port){
$('#tipMsg').append('Gate 连接成功! host:' + host + ' port:' + port + '<br/>'); //连接聊天服务器
pomelo.init({
host: host,
port: port,
log: true
}, function() {
var route = "connector.entryHandler.enter";
pomelo.request(route, {
username: uname,
rid: rid
}, function(data) {
if(data.error) {
$('#tipMsg').append('Chat 连接失败!<br/>');
return;
} $('#tipMsg').append('Chat 连接成功!<br/>'); $('#pnlLogin').hide();
$('#pnlChat').show(); $('#spUserName').text(uname);
$('#spRoomId').text(rid); //加载当前聊天室 已在线用户列表
$.each(data.users, function(i, item){
if(item != uname){
$('#selUserList').append('<option value="' + item + '">' + item + '</option>');
}
}); });
});
});
}); //发送消息
$('#btnSend').on('click', function(){
var route = "chat.chatHandler.send",
target = $("#selUserList").val(),
msg = $.trim($("#txtSendMessage").val()); if(msg.length == 0){
alert('不能发送空消息!');
return;
} pomelo.request(route, {
rid: rid,
content: msg,
from: uname,
target: target
}, function(data) { $("#txtSendMessage").val(''); if(from == uname) {
var name = (target == '*' ? '所有人' : target);
var time = getNowFormatDate(); $("#txtMessage").append('<span style="color:blue;">[' + time +'][' + from + '] 对 [' + name + '] 说: ' + msg + '<span><br/>');
} }); }); }) //连接Gate服务器
function queryEntry(uid, callback) {
var route = 'gate.gateHandler.queryEntry';
pomelo.init({
host: '127.0.0.1',
port: 15014,
log: true
}, function() {
pomelo.request(route, {
uid: uid
}, function(data) {
pomelo.disconnect();
if(data.code === 500) {
alert('用户名在此房间中已存在,请重新输入新的用户名!');
return;
}
callback(data.host, data.port);
});
});
}; function getNowFormatDate() {
var date = new Date();
var seperator1 = "-";
var seperator2 = ":";
var month = date.getMonth() + 1;
var strDate = date.getDate();
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (strDate >= 0 && strDate <= 9) {
strDate = "0" + strDate;
}
var currentdate = date.getFullYear() + seperator1 + month + seperator1 + strDate
+ " " + date.getHours() + seperator2 + date.getMinutes()
+ seperator2 + date.getSeconds();
return currentdate;
} </script>

app.js代码如下:

var express = require('express');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser'); var app = express(); var index = require('./routers/index.js'); // views engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs'); app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); app.use('/', index); app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
res.render('404');
}); if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
console.log(err);
res.status(err.status || 500);
res.render('500', {
message: err.message,
error: err
});
});
} app.use(function(err, req, res, next) {
console.log(err);
res.status(err.status || 500);
res.render('500', {
message: err.message,
error: {}
});
}); app.listen(10111, function () {
console.log("You can debug your app test with http://127.0.0.1:10111");
}); module.exports = app;

运行起来后,测试结果如下图:

写在之后

Pomelo学习入门不算复杂,写一篇感觉讲不全,写多篇感觉太散,大家将就着看,有些东西不认识的还是去看一下API文档 http://pomelo.netease.com/api.html  (也是低水准的官方API^_^!)

或者问一下google啥的...

可以参考这两个例子来学习:

https://github.com/NetEase/chatofpomelo

https://github.com/NetEase/lordofpomelo

入门建议从chatofpomelo开始

看之前可以提前看看一些pomelo术语,有个大体了解,再边看代码边理解:https://github.com/NetEase/pomelo/wiki/%E6%9C%AF%E8%AF%AD%E8%A7%A3%E9%87%8A

主要参考资料:

https://github.com/NetEase/pomelo/wiki/Home-in-Chinese  (比较杂乱,可能官方大神都忙着搞赚钱的项目,将就着看,有很多东西对入门来说还是很有用的)

如果有些问题解决不了,可以去社区问一下:http://nodejs.netease.com/tag/pomelo  (感觉现在活跃度也比较低^_^!)

文章来源:https://www.cnblogs.com/zhongweiv/p/nodejs_pomelo.html

Nodejs学习笔记(十六)—Pomelo介绍&入门的更多相关文章

  1. python3.4学习笔记(十六) windows下面安装easy_install和pip教程

    python3.4学习笔记(十六) windows下面安装easy_install和pip教程 easy_install和pip都是用来下载安装Python一个公共资源库PyPI的相关资源包的 首先安 ...

  2. Nodejs学习笔记(六)--- Node.js + Express 构建网站预备知识

    目录 前言 新建express项目并自定义路由规则 如何提取页面中的公共部分? 如何提交表单并接收参数? GET 方式 POST 方式 如何字符串加密? 如何使用session? 如何使用cookie ...

  3. Nodejs学习笔记(六)—Node.js + Express 构建网站预备知识

    前言 前面经过五篇Node.js的学习,基本可以开始动手构建一个网站应用了,先用这一篇了解一些构建网站的知识! 主要是些基础的东西... 如何去创建路由规则.如何去提交表单并接收表单项的值.如何去给密 ...

  4. (C/C++学习笔记) 十六. 预处理

    十六. 预处理 ● 关键字typeof 作用: 为一个已有的数据类型起一个或多个别名(alias), 从而增加了代码的可读性. typedef known_type_name new_type_nam ...

  5. python 学习笔记十六 django深入学习一 路由系统,模板,admin,数据库操作

    django 请求流程图 django 路由系统 在django中我们可以通过定义urls,让不同的url路由到不同的处理函数 from . import views urlpatterns = [ ...

  6. Java基础学习笔记十六 集合框架(二)

    List List接口的特点: 它是一个元素存取有序的集合.例如,存元素的顺序是11.22.33.那么集合中,元素的存储就是按照11.22.33的顺序完成的. 它是一个带有索引的集合,通过索引就可以精 ...

  7. angular学习笔记(十六) -- 过滤器(2)

    本篇主要介绍angular自定义的过滤器: 直接看例子: <!DOCTYPE html> <html ng-app="MyFilter"> <head ...

  8. angular学习笔记(十六) -- 过滤器(1)

    本篇主要介绍过滤器的基本用法: 过滤器用来对数据进行格式的转换,数据格式的转化与逻辑无关,因此,我们使用过滤器来进行这些操作: {{... | filter2: 参数1,参数2... }} expre ...

  9. C#线程学习笔记十:async & await入门三

    一.Task.Yield Task.Yield简单来说就是创建时就已经完成的Task,或者说执行时间为0的Task,或者说是空任务,也就是在创建时就将Task的IsCompeted值设置为0. 我们知 ...

  10. JavaScript权威设计--CSS(简要学习笔记十六)

    1.Document的一些特殊属性 document.lastModified document.URL document.title document.referrer document.domai ...

随机推荐

  1. Corel Video Studio Pro X5

    视频编辑也是大学的时候接触过,依稀记得转场,字幕,滤镜,电子相册等的概念.自己也不经常用,所以实践经验比较少.正好接一个机会学习一下视频编辑,用的是会声会影X5. 需要的软件Photoshop,格式工 ...

  2. jdk tomcat maven svn plsql客户端 环境变量配置整理

    1 jdk 新建: 1.JAVA_HOME   -----  C:\Program Files\Java\jdk1.7.0 2.CLASSPATH  ------   .;%JAVA_HOME%\li ...

  3. asp.net mvc 在JS中跳转到其它controller/action

    平时在ASP.NET 中经常这样写, $('#loginOut').click(function() {           $.messager.confirm('系统提示', '您确定要退出本次登 ...

  4. 解决 Eclipse Indigo 3.7、ADT 中文字体偏小,完美 Consolas 微软雅黑混合字体!

    Eclipse是著名的跨平台的自由集成开发环境(IDE).6月22日Eclipse 3.7 正式发布,代号是 Indigo . 在 Windows 7 下初始后化,发现界面变化不大,但中文字体却面目全 ...

  5. 构建NetCore应用框架之实战篇系列

    构建NetCore应用框架之实战篇 构建NetCore应用框架之实战篇(一):什么是框架,如何设计一个框架 构建NetCore应用框架之实战篇(二):BitAdminCore框架定位及架构 构建Net ...

  6. event 自定义事件

    自定义事件 1 public class Program 2 { 3 public event EventHandler ehdl=null; 4 public Program() 5 { 6 ehd ...

  7. kernel 调试 打印IP地址

    #define NIPQUAD(addr) \ ((unsigned char *)&addr)[0], \ ((unsigned char *)&addr)[1], \ ((unsi ...

  8. Exp2 后门原理与实践 20164321 王君陶

    Exp2 后门原理与实践 20164321 王君陶 一.实验内容 基础问题回答: 1.例举你能想到的一个后门进入到你系统中的可能方式? 答:通过漏洞,点击陌生链接,或者浏览不良网页挂马. 2.例举你知 ...

  9. 根据已有的Jar包 一键生成对应的mavenpom.xml信息

    根据已有的jar包信息一键生成对应的maven坐标信息 .想一个问题 假如 我有一个SSH的项目, jar包是配置在lib中, 我现在想把它做成maven格式的SSH项目  ,那么这些jar包在mav ...

  10. 牛客多校第四场 J.Hash Function(线段树优化建图+拓扑排序)

    题目传送门:https://www.nowcoder.com/acm/contest/142/J 题意:给一个hash table,求出字典序最小的插入序列,或者判断不合法. 分析: eg.对于序列{ ...