消息壓縮?
在實際編程中,為了減少數據傳輸帶寬的消耗,提高傳輸效率,pomelo提供了對消息的壓縮,包括基于字典的對route的壓縮和基于protobuf的對具體傳輸數據的壓縮。
route壓縮
在實際編程中,網絡帶寬的有效數據負載率是一個值得考慮的問題。特別地,對于移動客戶端來說,網絡資源往往并不是很豐富,為了盡可能地節省網絡資源,往往需要盡大可能地增加數據包的有效數據率。
route問題
在pomelo編程中,pomelo中的route是用來確定消息的分發路徑,將其交給相應的服務器和服務處理的。route分為兩類,由客戶端發給服務端消息時使用的route和服務端向客戶端廣播時使用的route。
-
前一種route是由服務器自動生成的,其中的字段就代表了對應的方法在服務端的位置。如“area.playerHandler.attack”則表示在“area”類型的服務器上的“playerHandler”提供的“attack”方法,其格式為".."。 路由信息過長,使得有效消息數據負載率大大降低。例如,在聊天應用中,如果用戶的發言僅僅是一個字符,結果不得不攜帶一個route,"chat.chatHandler.send",這樣使得有效數據負載率大大降低。
-
后一種route是服務端想客戶端推送消息時使用,是客戶端的路由信息,如“onMove”,“onAttack”等,其格式一般為"on"這些字段是由用戶自己定義的。雖然可以定義很短的路由,但是那樣會造成可讀性變差,不利于代碼閱讀。
一般來說,當應用固定后,具體路由就不會再變動,因此可以考慮通過一種簡單替換的方式對路由信息進行壓縮。
基于dict的壓縮
pomelo中實現了基于字典的route壓縮,目前route壓縮功能僅僅支持hybridconnector,sioconnector目前無法使用route壓縮。其實現原理如下:
-
對于系統生成的route,也就是服務端的路由信息,即格式為".."的路由信息,在系統啟動時由CoDictionary組件進行服務端路由信息掃描,然后會對每一個route生成唯一的字典項,由一個無符號小整數標識。
-
對于用戶自定義的route,也就是客戶端的路由信息,即格式為"on",則需要用戶提供一個自定義的route列表,會根據這一個列表對每個用戶自定義的route生成一個對應的字典項,即也就是一個無符號小整數。
-
在開啟字典功能的狀態下,使用hybridconnector的時候,當協議握手的時候,服務端會將整個字典的消息發送給客戶端,這樣客戶端和服務端都會擁有相同的具體route無符號整數的對應關系。
-
當有消息傳遞時,其中的route在發送時會被替換為在字典項,而接收端會自動還原,這一過程對于用戶而言是完全透明的。
pomelo的protobuf實現,借助了javascript的動態性,使得應用程序可以在運行時解析proto文件,不需要進行proto文件的編譯。
pomelo的實現中,為了更方便地解析proto文件,使用了json格式,與原生的proto文件語法是相通的,但是是不相同的。?
使用protobuf
雖然protobuf的實現看上去十分復雜,但由于這一層對用戶是完全透明的,使用會非常簡單。用戶只需要通過簡單的兩步定義就可以在原有的項目中開啟protobuf功能。
- 首先,需要在connector組件上打開protobuf開關,在app.js中的配置如下:
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));});
}
-
實際上需要加入的就是“useProtobuf:true”這一項。當設置這一標識后,pomelo會在客戶端握手時將protos內容同步到客戶端,并默認開啟protobuf壓縮功能。
-
在protobuf功能開啟用,用戶還需要加入protos定義來實現對具體消息的編碼/解碼。protos文件默認在/game-server/config目錄下,包括兩個文件:serverProtos.json和clientProtos.json,分別表示服務端->客戶端消息的protos和 客戶端->服務端消息的protos。只要在其中加入有效的proto定義,就可以開啟對應消息的protobuf編碼功能,CoProtobuf組件會自動加載這兩個proto文件。比如這樣定義一個serverProtos.json
{"onPickItem" : {"required uInt32 entityId" : 1,"required uInt32 target" : 2,"required uInt32 score" : 3},"rankUpdate" : {"repeated uInt32 entities" : 1} }
-
當然pomelo中的protobuf實現對原有項目是完全兼容的,你可以直接在老的項目中打開protobuf開關而不會引起任何問題。只是當proto定義是空的,默認所有的消息都不會經過protobuf壓縮,而是采用默認的二進制編碼進行傳輸。
-
當你想對某個消息進行protobuf編碼時,只需要在對應的protos文件(serverProtos.json或clientProtos.json)中加入對應的protobuf項,pomelo在啟動時就會自動識別并對消息進行壓縮,而不會對其他未定義的消息產生任何影響。
? ? ? 那么我提供游戲里的一部分源代碼,大家可以看下如何使用proto:
? 這里在pushMessage里,route配置的是serverProto.json里的onPickItem,那么就會按照onPickItem的格式來生成編碼,里邊的字段包含了下面賦值的部分。
player.on('pickItem', function(args) {var player = self.getEntity(args.entityId);var treasure = self.getEntity(args.target);player.target = null;if (treasure) {player.addScore(treasure.score);self.removeEntity(args.target);self.getChannel().pushMessage({route: 'onPickItem',entityId: args.entityId,target: args.target,score: treasure.score});}});
?