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

来源:http://www.cnblogs.com/zhongweiv/archive/2017/11/24/nodejs_pomelo.html
-Advertisement-
Play Games

目錄 前言&介紹 安裝Pomelo 創建項目並啟動 創建項目 項目結構說明 啟動 測試連接 聊天伺服器 新建gate和chat伺服器 配置master.json 配置servers.json 配置adminServer.json 解決伺服器分配問題 實現gate.gateHandler.queryE ...


目錄

前言&介紹

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
  }
}
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個鏈接

 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”文件,處理此伺服器分配邏輯

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'	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>設計中文版式</title> 6 <style type="text/css"> 7 body{ /*頁面基本屬性*/ 8 backgrou ...
  • 【以下作品非原創,僅學慣用】 <style> html,body{ width: 100%;height: 100%;overflow: hidden; } </style></head><body> <canvas id="cav"></canvas> <script> var cav = doc ...
  • HTML 中的預留字元必須被替換為字元實體。 HTML 實體 在 HTML 中,某些字元是預留的。 在 HTML 中不能使用小於號(<)和大於號(>),這是因為瀏覽器會誤認為它們是標簽。 如果希望正確地顯示預留字元,我們必須在 HTML 源代碼中使用字元實體(character entities)。 ...
  • ECMAScript 6.0(簡稱 ES6)是 JavaScript 語言的下一代標準,它於2015 年 6 月正式發佈。ECMAScript 和 JavaScript 的關係是,前者是後者的規格,後者是前者的一種實現。ECMAScript實現還有Jscript和ActionScript。 ...
  • 最近做一個自定義視覺效果的Switch組件,用到了 input:radio 和 label,併在label里用偽元素 :before 模擬狀態的切換效果。 但是同事評審的時候說可以不用label,直接用input的微元素就可以實現。之前一直以為input這樣的自閉合元素沒有偽元素,做了個測試看一下到 ...
  • 最近幾年web前端開發領域最熱的話題當屬HTML5,HTML5從根本上改變了開發商開發web應用的方式,從桌面瀏覽器到移動應用,這種語言和標準都正在影響並將繼續影響著各種操作平臺。 ...
  • 前言 一直在學習 javascript,也有看過《犀利開發 Jquery 內核詳解與實踐》,對這本書的評價只有兩個字犀利,可能是對 javascript 理解的還不夠透徹異或是自己太笨,更多的是自己不擅於思考懶得思考以至於裡面說的一些精髓都沒有太深入的理解。 鑒於想讓自己有一個提升,進不了一個更加廣 ...
  • 轉載:http://blog.csdn.net/piaoxuan1987/article/details/8541839 equest.getRealPath() 這個方法已經不推薦使用了,代替方法是: request.getSession().getServletContext().getReal ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...