(十四)nodejs循序漸進-高性能游戲服務器框架pomelo之開發Treasures游戲

#Tutorial 2 -- Treasures ##描述?Treasures?游戲是從?LordOfPomelo?中抽取出來,去掉了大量的游戲邏輯,用以更好的展示?Pomelo?框架的用法以及運作機制。

Treasures 很簡單,輸入一個用戶名后,會隨機得到一個游戲角色,進入游戲場景。在游戲場景中地上會散落一些寶物,每個寶物都有分數,玩家操作游戲人物去撿起地上的寶物,然后就能得到相應的分數。

##安裝和運行 安裝?pomelo

npm install -g pomelo

獲取源碼

git clone https://github.com/NetEase/treasures.git

安裝?npm?依賴包(先進入項目目錄)

sh npm-install.sh

啟動?web-server?(先進入web-server目錄)

node app.js

啟動?game-server?(先進入game-server目錄)

pomelo start

在瀏覽器中訪問?http://localhost:3001?進入游戲

Pomelo自帶的demo只Treasures運行時報錯解決方案:

Pomelo自帶demo之Treasures,下載源碼后進入web-server目錄,先輸入命令 npm install -d 安裝第三方模塊,之后運行會報錯,錯誤提示是:TypeError: mime.lookup is not a function,web-server\node_modules\connect\lib\middleware\static.js:144。原因是mime模塊從2.x版本開始把lookup方法改為了getType,而作者編寫時的mime模塊是1.x版本。如果把lookup改為getType,雖能解決眼前這個錯誤,但接下來還會有其它地方報錯,因為2.x版本不僅僅修改了這一個方法。更簡單的解決辦法是直接使用mime的1.x版本。方法有2種:

1、在web-server目錄下運行命令:npm install mime
2、打開web-server目錄的package.json,在dependencies字段中增加一行:"mime": "^1",意思是安裝mime的1.x版本。之后運行命令:npm install -d

第1種方法雖簡單,但如果把源碼clone到別處,問題會重復出現,所以推薦使用第2種方法,在package.json明確好版本,不管clone到哪,只要npm install -d就好了。

?

##架構 Treasures 分為 web-Server 和 game-Server 兩部分。

  • web-server?是用 Express 建立的最一個基礎的 http 服務,用來支撐瀏覽器頁面的訪問。

  • game-server?是 WebSocket 服務器,用來運行整個游戲的邏輯。

首先,通過配置文件,來看?game-server?的具體架構?game-server/config/server.json

{"development": {"connector": [{"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true},{"id": "connector-server-2", "host": "127.0.0.1", "port": 3151, "clientPort": 3011, "frontend": true}],"area": [{"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "areaId": 1}],"gate": [{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}]}
}

可以看出,服務端是由以下幾個部分構成:

  • 2 個?connector?服務器,主要用于接受和發送消息。
  • 1 個?gate?服務器,主要用于負載均衡,將來自客戶端的連接分散到兩個?connector?服務器上。
  • 1 個?area?服務器,主要用于驅動游戲場景,和游戲邏輯

##源碼分析 通過游戲流程來分析代碼。

1. 連接服務器 客戶端?web-server/public/js/main.js?中?entry?方法中

pomelo.request('gate.gateHandler.queryEntry', {uid: name}, function(data) {//...
});

服務端?game-server/app/servers/gate/handler/gateHandler.js?中

Handler.prototype.queryEntry = function(msg, session, next) {// ...// 返回要連接的 connector 服務器的 host 和 portnext(null, {code: Code.OK, host: res.host, port: res.wsPort});
};

這樣客戶端就能連接到分配的?connector?服務器上。

2. 進入游戲 在與?connector?服務器建立連接之后,開始進入游戲

pomelo.request('connector.entryHandler.entry', {name: name}, function(data) {// ...
});

在客戶端第一次向?connector?服務器發送請求時,服務器會將?session?信息進行初始化和綁定

// session 與 playerId 綁定
session.bind(playerId);
// 設置玩家 areaId
session.set('areaId', 1);

進入游戲場景,客戶端向服務端發起進入場景請求:

pomelo.request("area.playerHandler.enterScene", {name: name, playerId: data.playerId}, function(data) {// ...
});

客戶端向服務端發送請求后,先到達?connector?服務器,然后?connector?服務器根據?game-server/app/util/routeUtil.js?中轉發規則,將請求路由到相應的?area?服務器(本例子中只有一個area服務器),area?服務器中的?playerHandler?再處理相應的請求。這樣玩家就加入到游戲場景中了。

在一個玩家加入到游戲場景之后,其他玩家必須能即時的看到這個玩家的加入,所以服務端必須將消息廣播到在此游戲場景中的所有玩家。 建立?channel,所有加入此游戲場景的玩家都會加入到這個?channel?中

// 獲取 channel,如果沒有就創建一個
channel = pomelo.app.get('channelService').getChannel('area_' + id, true);
// 將玩家加入 channel
channel.add(e.id, e.serverId);

當?area?中有玩家加入,或其他狀態發生改變時,這些信息都會被推送到在這個?channel?中的每個玩家。比如有玩家加入時:

channel.pushMessage({route: 'addEntities', entities: added});

這些消息都是通過?connector?服務器發送到客戶端。而?area?中的消息是通過?session.frontendId?來決定是由哪個?connector?服務器發出去。

客戶端接受消息:

// 當有新玩家加入時,服務端會廣播消息給所有玩家。客戶端通過這個路由綁定,來獲取消息
pomelo.on('addEntities', function(data) {// ...
});

3. Area 服務器?area?服務器是一個由?tick?驅動的游戲場景。每個?tick?都會對場景中的?entity?的狀態進行更新,如果狀態有發生改變,這些改變會被推送到客戶端。

function tick() {//run all the actionarea.actionManager().update();// update entitiesarea.entityUpdate();// update rankarea.rankUpdate();
}

比如玩家發起一個?move?動作:

客戶端

// 向服務端發送 move 請求通知
pomelo.notify('area.playerHandler.move', {targetPos: {x: entity.x, y: entity.y}, target: targetId});

服務端?playerHandler?接受請求:

handler.move = function(msg, session, next) {// ...// 產生一個 move actionvar action = new Move({entity: player,endPos: endPos,});
});

然后這個?action?會在每個?tick?中更新。

###4. 客戶端發送和接受消息 客戶端和服務端的通訊有以下幾種方式:

  • Request - Response 方式
// 向 connector 發送請求,參數 {name: name}
pomelo.request('connector.entryHandler.entry', {name: name}, function(data) {// 回調函數得到請求返回結果// do something
});
  • Notify (向服務端發送通知)
// 向服務端發送 move 請求通知
pomelo.notify('area.playerHandler.move', {targetPos: {x: entity.x, y: entity.y}, target: targetId});
  • Push (服務端主動發送消息到客戶端)
// 當有新玩家加入時,服務端會廣播消息給所有玩家。客戶端通過這個路由綁定,來獲取消息
pomelo.on('addEntities', function(data) {// ...
});

###5. 離開游戲 就是在玩家離開游戲時,connector?服務器會先收到斷開的消息,這時,它需要在?area?服務器中將用戶剔除,并廣播消息給其他在線玩家。 因為服務器之間的進程都是獨立的,所以這就涉及到一個 RPC 調用,好在 Pomelo 框架對 RPC 做了很好的封裝,做法如下:?area?服務器想要提供一系列的 Remote 接口供其他服務器進程調用,只需要在?servers/area?目錄下,創建一個?remote?目錄,在這個目錄下的文件暴露出的接口,都可以作為 RPC 調用接口。 比如,玩家離開:

// connector 中對 session 綁定事件,當 session 關閉時,觸發事件
session.on('closed', onUserLeave.bind(null, self.app));var onUserLeave = function (app, session, reason) {if (session && session.uid) {// rpc 調用app.rpc.area.playerRemote.playerLeave(session, {playerId: session.get('playerId'), areaId: session.get('areaId')}, null);}
};

對應的?area/remote/playerRemote.js?中?playerLeave?方法

exports.playerLeave = function(args, cb) {// 發出通知area.getChannel().pushMessage({route: 'onUserLeave', code: consts.MESSAGE.RES, playerId: playerId});// ...
};

這樣就輕易的完成了一個跨進程的調用?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444492.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444492.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444492.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

leetcode243. 最短單詞距離(vip題)好像挺簡單?

給定一個單詞列表和兩個單詞 word1 和 word2,返回列表中這兩個單詞之間的最短距離。 示例: 假設 words ["practice", "makes", "perfect", "coding", "makes"] 輸入: word1 “coding”, word2 “practice”…

談談蘋果應用內支付(IAP)的坑

一、請求商品 下面是請求商品的代碼: - (void)validateProductIdentifier:(NSArray *)productIdentifier {SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifier]];self.request = productRe…

leetcode204. 計數質數(vip題)

統計所有小于非負整數 n 的質數的數量。 示例: 輸入: 10 輸出: 4 解釋: 小于 10 的質數一共有 4 個, 它們是 2, 3, 5, 7 。 思路:篩法,見代碼。 class Solution {public int countPrimes(int n) {// 1. 給數加上標記byte[] nums new byte[n];for (i…

如何使得客戶端和服務器端完美配合做IOS應用內付費

配置Developer.apple.com 登錄到Developer.apple.com,然后進行以下步驟: 為應用建立建立一個不帶通配符的App ID用該App ID生成和安裝相應的Provisioning Profile文件。配置iTunes Connect 登錄到iTunes Connet,然后進行以下步驟: 用該App ID創建一個新的應用。在該應用中…

IOS內購流程從0-1手把手教會

蘋果掌握著可能是全球最重要的APP分發渠道,然而30%的抽成近年來也被人批評,現在蘋果似乎也看到反對意見了,從2021年1月1日開始,部分小型企業的分成費用降低到15%。 據報道,蘋果將于2021年1月1日啟動App Store小企業項目,會降低他們的抽成費用。針對年收入不足100萬美元的…

leetcode217. 存在重復元素(vip題)超簡單

給定一個整數數組,判斷是否存在重復元素。 如果任何值在數組中出現至少兩次,函數返回 true。如果數組中每個元素都不相同,則返回 false。 示例 1: 輸入: [1,2,3,1] 輸出: true 示例 2: 輸入: [1,2,3,4] 輸出: false 示例 3: 輸入: [1,1,…

訂單數據持久化和驗證相關解決方案

訂單數據持久化 有時候蘋果支付在支付完成后,從蘋果服務器返回收據的過程中可能會掉單(可能是網絡問題,可能是蘋果BUG,也有一部分是開發者自身埋的坑),因此我們需要一個訂單持久化的機制來保障。 首先根據內購商品ID(此商品ID是在蘋果后臺建好的內購商品)、用戶信息(…

IOS iap處理邏輯流程圖再次梳理

序言: 本文補全一下iOS iap處理邏輯。 iap處理邏輯 蘋果退單wiki:https://developer.apple.com/documentation/storekit/in-app_purchase/handling_refund_notifications 一、上圖主要處理了以下業務: 普通購買 自動續訂訂閱 補單處理 預防黑產 退單處理 二、除了上述業…

(十七)深入淺出TCPIP之HTTP和HTTPS

超文本傳輸協議HTTP協議被用于在Web瀏覽器和網站服務器之間傳遞信息,HTTP協議以明文方式發送內容,不提供任何方式的數據加密,如果攻擊者截取了Web瀏覽器和網站服務器之間的傳輸報文,就可以直接讀懂其中的信息,因此&…

leetcode283. 移動零 比官方更好的解法。

給定一個數組 nums,編寫一個函數將所有 0 移動到數組的末尾,同時保持非零元素的相對順序。 示例: 輸入: [0,1,0,3,12] 輸出: [1,3,12,0,0] 說明: 必須在原數組上操作,不能拷貝額外的數組。 盡量減少操作次數。 思路:記錄0的個…

C++:15---異常機制

1.概念:異常處理是一種允許兩個獨立開發的程序組件在程序執行時遇到不正常的情況相互通信的工具 2.異常檢測和異常處理的方式throw表達式:程序遇到了錯誤或者無法處理的問題,使用throw引發異常try、catch語句塊:以關鍵字tyr開始,并以一個或多個catch子句結束。它們也被稱為…

Redis:08---字符串對象

一、字符串對象概述字符串類型是Redis最基礎的數據結構。首先鍵都是字符串類型,而且其他幾種數據結構都是在字符串類型基礎上構建的,所以字符串類型能為其他四種數據結構的學習奠定基礎字符串就是一個由字節組成的序列如下圖所示,字符串類型的…

leetcode252. 會議室

給定一個會議時間安排的數組&#xff0c;每個會議時間都會包括開始和結束的時間 [[s1,e1],[s2,e2],...] (si < ei)&#xff0c;請你判斷一個人是否能夠參加這里面的全部會議。 示例 1: 輸入: [[0,30],[5,10],[15,20]] 輸出: false 示例 2: 輸入: [[7,10],[2,4]] 輸出: tr…

(十八)深入淺出TCPIP之epoll的一些思考

Epoll基本介紹在linux的網絡編程中&#xff0c;很長的時間都在使用select來做事件觸發。在linux新的內核中&#xff0c;有了一種替換它的機制&#xff0c;就是epoll。相比于 select&#xff0c;epoll最大的好處在于它不會隨著監聽fd數目的增長而降低效率。因為在內核中的select…

leetcode292. Nim 游戲

你和你的朋友&#xff0c;兩個人一起玩 Nim 游戲&#xff1a;桌子上有一堆石頭&#xff0c;每次你們輪流拿掉 1 - 3 塊石頭。 拿掉最后一塊石頭的人就是獲勝者。你作為先手。 你們是聰明人&#xff0c;每一步都是最優解。 編寫一個函數&#xff0c;來判斷你是否可以在給定石頭…

C++:16---強制類型轉換和類型轉換

舊式的強制類型轉換 在早期C/C++中,顯式地進行強制類型的轉換有以下兩種形式:type (expr) ; //函數形式的強制類型轉換(type) expr; //C語言風格的強制類型轉換比如: char c = 12; int b = (int)c; float f = float(b); C++的新式強制類型轉換…

Nginx不停機優雅升級

最近線上運行的游戲越來越多,云服務商也給我推送提示系統升級,漏洞補丁升級,也有nginx更新的。 有一些比較關鍵性的系統補丁需要立即更新處理,有一些可以換一換不用升級,但此nginx升級的需求比較迫切,但更新可能需要重啟nginx。 這將會影響到我們這樣的一個登錄業務邏輯…

leetcode186. 翻轉字符串里的單詞 II

給定一個字符串&#xff0c;逐個翻轉字符串中的每個單詞。 示例&#xff1a; 輸入: ["t","h","e"," ","s","k","y"," ","i","s"," ","b","l…

Nginx大規模并發原理

Nginx在主流硬件上的并發數為十萬,網絡處理方面的領先地位,歸功于突破性的事件驅動架構。 Nginx在每顆內核上創建一個工作進程,有效利用硬件資源。 在單個工作進程中交替處理多個連接,應對突如其來的網絡流量。 Nginx資源管理 Nginx使用狀態機管理流量。 非阻塞事件…

使用 CXF 做 webservice 簡單例子

轉&#xff1a;http://www.cnblogs.com/frankliiu-java/articles/1641949.html Apache CXF 是一個開放源代碼框架&#xff0c;提供了用于方便地構建和開發 Web 服務的可靠基礎架構。它允許創建高性能和可擴展的服務&#xff0c;您可以將這樣的服務部署在 Tomcat 和基于 Spring …