1. 程式人生 > >Nodejs學習筆記(十六)--- Pomelo介紹&入門

Nodejs學習筆記(十六)--- Pomelo介紹&入門

目錄

前言&介紹

Pomelo:一個快速、可擴充套件、Node.js分散式遊戲伺服器框架

從三四年前接觸Node.js開始就接觸到了Pomelo,從Pomelo最初的版本到現在,總的來說網易出品還算不錯,但是發展不算快;用它做過一些專案和小遊戲表現還不錯。

用它的主要好處:

1. 入門簡單,有比較豐富的文件和示例(雖然現在看版本也比較老了,但是入門沒什麼問題)

2.分散式多程序且擴充套件簡單(單程序多執行緒,每個伺服器都是一個Node程序,通過配置檔案就可以管理叢集)

3.可以不去關注底層和網路相關邏輯,聚焦業務邏輯的處理,對於有Web伺服器開發經驗卻沒有遊戲伺服器開發經驗來說還是比較友好的

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

......  

  入門參考連結

  其它連結:  

安裝Pomelo

  安裝要求  

Windows下安裝要求環境

Python (2.5 < 版本 < 3)

VC++編譯器

PS:  Windows新環境自已檢查一下,我本機環境已經裝好python2.7,Visaul Studio也安裝了所以也有VC++編譯器

  其它作業系統應該問題不大

    全域性安裝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

替換後再啟動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,要入門還是以一個聊天伺服器為入門示例最好,其它邏輯相對簡單,入門學習不會因其它遊戲邏輯影響。

 網上也有很多文章分析講解這專案,我就不完全解釋些專案了,接下來我就在上面新建的好的“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的操作。

 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
  }
}
master.json

  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}
        ]
  }
}
servers.json

  解釋一下配置中的各欄位:

  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"
}]
adminServer.json

 它有什麼作用,可以看一下以下2個連結

 5.解決伺服器分配問題

 從上面的servers.json配置的修改可以看出與最開始創建出來的專案一個伺服器相比,connector和chat我都配置了三個伺服器

 這就要解決客戶端請求伺服器分配問題

 解決思路:使用者訪問gate伺服器,使用使用者的uid的crc32的校驗碼與connector伺服器的個數取餘,從而得到一個connector伺服器,把這個connector伺服器分配給請求使用者

 在app目錄下新建util目錄,目錄下新建“dispatcher.js”和 “routeUtil.js”檔案,處理此伺服器分配邏輯

var crc = require('crc');

module.exports.dispatch = function(uid, connectors) {
    var index = Math.abs(crc.crc32(uid)) % connectors.length;
    return connectors[index];
};
dispatcher.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);
};
routeUtil.js

  準備好這些檔案後,在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
    });
};
gateHandler.js

 7.實現chat伺服器chatRemote.js 

 chat伺服器會接受connector的遠端呼叫,完成channel維護中的使用者的加入以及離開

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

 可以看到上面程式碼中的add和kick分別對應著加入和離開channel

 8.實現chat伺服器chatHandler.js

 chat伺服器執行聊天邏輯,維護channel資訊,一個房間就是一個channel,一個channel裡有多個使用者,當有使用者發起聊天的時候,就會將其內容廣播到整個channel。

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
    });
};
chatHandler.js

 這裡面是傳送訊息(給房間內所有人和指定使用者)

 9.實現connector中entryHandler.js

  主要完成接受客戶端的請求,維護與客戶端的連線,路由客戶端的請求到chat伺服器;

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

  這裡完成的主要就是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;
index.js

 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;
app.js

 執行起來後,測試結果如下圖:

寫在之後

   Pomelo學習入門不算複雜,寫一篇感覺講不全,寫多篇感覺太散,大家將就著看,有些東西不認識的還是去看一下API文件 http://pomelo.netease.com/api.html  (也是低水準的官方API^_^!)

   或者問一下google啥的... 

   可以參考這兩個例子來學習:

   入門建議從chatofpomelo開始

   主要參考資料:

相關推薦

Nodejs學習筆記--- Pomelo介紹&入門

目錄 前言&介紹 Pomelo:一個快速、可擴充套件、Node.js分散式遊戲伺服器框架 從三四年前接觸Node.js開始就接觸到了Pomelo,從Pomelo最初的版本到現在,總的來說網易出品還算不錯,但是發展不算快;用它做過一些專案和小遊戲表現還不錯。 用它的主要好處:

Nodejs學習筆記Pomelo介紹&入門

錯誤信息 develop prototype client 之前 clas zhong 早已 express 前言&介紹   Pomelo:一個快速、可擴展、Node.js分布式遊戲服務器框架   從三四年前接觸Node.js開始就接觸到了Pomelo,從Pomel

Nodejs學習筆記—Mongoose介紹入門

tar 字段名 lse number 數組 int 位置 斷開 mongod 簡介   Mongoose是在node.js異步環境下對mongodb進行便捷操作的對象模型工具   那麽要使用它,首先你得裝上node.js和mongodb,關於mongodb的安裝和操作介紹可

Nodejs學習筆記— Mongoose介紹入門 && Nodejs學習筆記--- 與MongoDB的互動mongodb/node-mongodb-native、MongoDB入門

目錄 簡介   MongoDB    開源,高效能的NoSQL資料庫;支援索引、叢集、複製和故障轉移、各種語言的驅動程式;高伸縮性;   node-mongodb-native   mongodb的nodejs驅動; MongoDB安裝(windows)   按照官方說明在win7 64位

Nodejs學習筆記— Mongoose介紹入門

目錄 簡介   Mongoose是在node.js非同步環境下對mongodb進行便捷操作的物件模型工具   前面有介紹過用node-mongodb-native來操作mongodb,實際開發中估計更多會選用類似mongoose的模組來操作來提升開發效率   下面我們一

Python學習筆記拓展庫Scipy

mage http 十六 .cn 學習筆記 eight -1 images 分享 Python學習筆記(十六)拓展庫Scipy

Java學習筆記:static關鍵字

ima 關鍵字 static關鍵字 es2017 java學習筆記 sta com 筆記 nbsp Java學習筆記(十六):static關鍵字

R語言學習筆記:處理缺失值

ima 結果 cti img dataset case prop .com log #識別缺失值 install.packages("VIM") data(sleep,package="VIM") #列出沒有缺失值的行 sleep[complete.case

Ionic3學習筆記上傳頭像至圖床

string con error wid nat file targe ict avatar 本文為原創文章,轉載請標明出處 個人做的開源 Demo 登錄註冊模塊采用的是 Wilddog 野狗通訊雲的身份認證服務,不得不說各方面和 Google 收購的 Firebase

Nodejs學習筆記—數據采集器示例request和cheerio

列表 意思 9.1 很多 AD 開發 com http undefined 寫在之前   很多人都有做數據采集的需求,用不同的語言,不同的方式都能實現,我以前也用C#寫過,主要還是發送各類請求和正則解析數據比較繁瑣些,總體來說沒啥不好的,就是效率要差一些,   用nodej

Nodejs學習筆記—定時任務node-schedule)

sch 接下來 bsp 消息 分享 學習筆記 day 筆記 定時器 寫在之前   在實際開發項目中,會遇到很多定時任務的工作。比如:定時導出某些數據、定時發送消息或郵件給用戶、定時備份什麽類型的文件等等   一般可以寫個定時器,來完成相應的需求,在node.js中自已實現也

Nodejs學習筆記—浮點運算decimal.js

學習筆記 存儲 log title ron 學習 decimal 安裝 bit 前言   開發過程中免不了有浮點運算,JavaScript浮點運算的精度問題會帶來一些困擾   JavaScript 只有一種數字類型 ( Number )    JavaScript采用

javaweb學習筆記:JDBC2

批處理 當需要向資料庫傳送一批SQL語句執行時,應避免向資料庫一條條的傳送執行,而應採用JDBC的批處理機制,以提升執行效率。 實現批處理有兩種方式: ①Statement.addBatch(sql) :新增批處理命令。 優點:可以向資料庫傳送多條不同的SQL語句。 缺點:S

Effective_STL 學習筆記 如何將 vector 和 string 的數據傳給遺留的API

lar 內存分配 修改 叠代 元素 amp const 兼容 con 已經存在的遺留的 C 風格 API 接受的是數組和 char* 指針,這樣的 API 函數還將會存在很長時間,如果我們要有效使用 STL 的話,就必須和它們和平共處。 如果有一個 vector 對

python基礎教程第三版)學習筆記

第十六章 測試基礎 除錯是程式設計師躲不開的宿命,是程式設計工作的有機組成部分。 本章介紹測試的基本知識。培養如何養成在程式設計中進行測試的習慣,並介紹一些可幫 助編寫測試的工具。 16.1 先測試再編碼 要避免程式碼在開發途中被淘汰,必須能夠應對變化並具備一定的靈活性,因此為程式的各個

機器學習筆記:TensorFlow實戰八經典卷積神經網路:GoogLeNet

1 - 引言 GoogLeNet, 在2014年ILSVRC挑戰賽獲得冠軍,將Top5 的錯誤率降低到6.67%. 一個22層的深度網路 論文地址:http://arxiv.org/pdf/1409.4842v1.pdf 題目為:Going deeper with convolu

GoLang學習筆記指標

指標是儲存另一個變數的記憶體地址的變數。 變數是一種使用方便的佔位符,變數都只想計算機的記憶體地址。 一個指標變數可以指向任何一個值的記憶體地址 GoLang獲取一個變數的地址,在一個變數前使用&符號,會返回該變數的記憶體地址. GoLang的指標是不能參與運算的。 宣告指標,*T是指標

Python學習筆記模組

模組是Python程式架構的一個核心概念   模組就好比是工具包,要想使用這個工具包中的工具,就需要匯入import這個模組 每一個以副檔名py結尾的Python原始碼檔案都是一個模組 在模組中定義的全域性變數、函式都是模組能夠提供給外界直接使用的工具 &nb

Python學習筆記模塊

編寫 pytho pychar 擴展名 能夠 一個 每一個 註意 源代碼 模塊是Python程序架構的一個核心概念 模塊就好比是工具包,要想使用這個工具包中的工具,就需要導入import這個模塊 每一個以擴展名py結尾的Python源代碼文件都是一個模塊 在模塊中定義

Android開發學習筆記基礎UI控制元件之ListView-SimpleAdapter

一、ListView控制元件: <ListView android:id="@+id/list_view_demo" android:layout_width="match_pa