Skip to content

Q4`24-Slots- websocket 协议压缩 Gzip

修订人:赵恒 修订时间: 2024年12月19日

版本节点:classic_vegas_cvs_v777_gzlib

投放分支:

一、概念、术语、类型约定( 后文以此为准):


Gzip:GNU zip 基于 DEFLATE 算法;无损压缩;跨平台;压缩比率高;解压速度快;网络传输效率提升; 网页内容压缩,日志文件压缩,数据库备份压缩,软件包分发;API 响应压缩;

前端 pomelo 库:

"@me2zen/pomelo-cocos2d-js@0.1.14"
- "@me2zen/pomelo-jsclient-websocket": "^0.1.5",
– "@me2zen/pomelo-protocol": "^0.1.6",

二、需求:


服务器回传数据做 gzip 压缩:

1、降本增效;
2、高频、大数据量协议,传输、响应压缩;

三、实现方法:


1、增加控制字段 openGzip:

前后端约定并保持一致

image1
image2

2、路由分离: 修正 protocol route,需支持压缩的协议,增加 _gzip 路由;

前后端约定并保持一致:
原因:客户端收到返回协议,经过 decode 后的 msgbody 统一为 Uint8Array 类型,无法仅通过 msgbody 类型区分是否为需解压数据;

image3

3、后端 gzip 压缩: 在约定开启 gzip 的协议返回前,对协议进行 gzip 压缩;

Q4'24-Center-Tech-Slots-Slots压缩

4、前端 gunzip 解压: 修正 pomelo-jsclient-websocket 库源码,根据返回协议的 route,做 gunzip 解压:

前端协议代码响应路径:

webcocket.onMessage
Pomelo.Package.decode
Pomelo.processPackage
Pomelo.onData
Pomelo.Message.decode
Pomelo.doCompress
Pomelo.processMessage
Pomelo.onProtocol
… 业务层

核心修改:pomelo-client.js

var onData = function(data) {
var msg = Message.decode(data);

if(msg.id > 0){
msg.route = routeMap[msg.id];
delete routeMap[msg.id];
if(!msg.route){
processMessage(pomelo, msg);
return;
}
}

msg.body = deCompose(msg);

if(msg.route && msg.route.indexOf && msg.route.indexOf("_gzip") > -1) {
zlib.gunzip(new Buffer(msg.body), function (error, unzip_d) {
if (error) {
processMessage(pomelo, msg);
console.warn("gunzip error:", msg);
return;
}

 // console.warn("pomelo gunzip result:", msg.route, Protocol.strdecode(unzip\_d));  
 msg.body \= JSON.parse(Protocol.strdecode(unzip\_d));  
 processMessage(pomelo, msg);  

});
return;
}

processMessage(pomelo, msg);
//#endregion
};

var deCompose = function(msg) {
var route = msg.route;

//Decompose route from dict
if(msg.compressRoute) {
if(!abbrs[route]){
return {};
}

route = msg.route = abbrs[route];
}

if(protobuf && serverProtos[route]) {
return protobuf.decodeStr(route, msg.body);
} else if(decodeIO_decoder && decodeIO_decoder.lookup(route)) {
return decodeIO_decoder.build(route).decode(msg.body);
} else {
//#region Support gunzip compression
if(route && route.indexOf && route.indexOf("_gzip") > -1) {
return msg.body;
}
//#endregion

return JSON.parse(Protocol.strdecode(msg.body));
}

return msg;
};

5、[附加] 日志上报接口压缩:

服务器:

image4

客户端:

var headers = {};
headers["Content-Type"] = "application/json;charset=UTF-8";
headers["Content-Encoding"] = "gzip";
var zlib = require("zlib");
zlib.gzip(JSON.stringify(sendObj), function (error, data) {
HttpClient.doPost(url, data, headers, function (error, txt) {
if (error) {
cc.log("HttpClient doPost error:" + error + ", " + txt);
}
});
});

四、注意问题:


1、Slots 前端并没有使用 pomelo-cocos2d-js,而是直接使用其依赖库 pomelo-jsclient-websocket,pomelo-cocos2d-js 本身只用来做依赖导入;

2、Slots 前端并没有基于 EventEmitter 重新封装 Pomelo 类;而是直接使用了 window.pomelo ,window.pomelo 指向 pomelo-jsclient-websocket 库,通过 pomelo.on 注入基本的响应事件;

3、Npm包维护问题:

3.1、修改 npm 包 git 引用地址;需要注意,git 读取权限和项目仓库权限绑定;

3.2、npm提交一个新版本;需要对 pomelo-cocos2d-js,pomelo-jsclient-websocket 同步提交版本,避免影响其他已经在使用的项目;

4、Slots-Native 端使用的 spiadermonkey 引擎,对 sync 语法支持有限,尽量使用 zlib.gunzip();

5、注意检查 pomelo 库版本差异,注意检查前后端 deCompress 代码差异;

6、gzip 适合压缩文本文件,图片、视频等已压缩文件不适合再压缩;

7、gzip 可定义压缩级别,压缩级别越高 CPU 消耗越高;需平衡压缩率和 CPU 资源消耗;

8、小文件不适合 gzip 压缩;

9、避免重复压缩;

五、验证、自测:


效果验证:

对比工具:Chrome Debug Tool -> 网络 -> WS |native -> charles 使用说明

image5

1、相同服务器,相同等级账号,login协议对比:

压缩前:2.5K,压缩后:1.4K

2、同一个服务器、相同等级账号、相同关卡、相同结果,enter_room协议对比:

压缩前:9.1K,压缩后:2.1K

3、同一个服务器、相同等级账号、相同关卡、相同结果,spin协议对比:

压缩前:1.4K,压缩后:464B

测试点:

  • [ ] #### 1、涉及压缩的协议;(主要)

  • [ ] #### 2、后端主动推送协议是否正常;(主要)

  • [ ] #### 3、native 相关协议是否正常;(主要)

  • [ ] #### 4、极端条件测试:弱网;(次要)

  • [ ] #### 5、新老包兼容测试;(次要)

  • [ ] #### 6、服务器升级,客户端不更新测试;(次要)

六、补充:

npm git 仓库:
https://github.com/LuckyZen/pomelo-cocos2d-js
https://github.com/LuckyZen/pomelo-jsclient-websocket
https://github.com/LuckyZen/pomelo-protocol

Released under the MIT License.