在测试ss代理客户端或服务端逻辑时,工程师往往希望直观看到流量随时间的变化情况。借助Node.js,我们可以编写一个名为ss Mock2Image的工具,将模拟的ss交互数据转换成一张可视化图片,便于排查异常或做汇报材料。

一、整体设计思路
ss Mock2Image并不是去解密真实的ss加密流量,而是在本地用Mock方式构造ss风格的请求与响应,记录每一次收发包的时间戳、长度和方向,再把这些结构化数据送给图像绘制模块。这样做既绕开了加密难题,也能稳定产出可重复的样例图。
整个流程分为三个独立阶段。第一阶段是流量采集器,利用Node.js的net模块监听指定端口,把进出的Buffer存入数组;第二阶段是数据归一化,将原始字节流按毫秒级时间窗汇总;第三阶段调用canvas库,在内存里画出坐标轴和柱状条,最后导出为PNG文件。这种分层方式让每一部分都能单独单元测试。
1.1 为什么选Node.js
Node.js的事件循环非常适合处理大量小数据包的并发读写,而且npm上有成熟的canvas和pcap辅助包。相比用Python写脚本再调绘图工具,纯JS方案部署更简单,一台装了Node环境的机器就能跑。
另外,前端同学如果熟悉JS,就可以直接改绘制逻辑,不需要切换语言上下文。对于需要把工具集成进现有Node服务的团队来说,这也是最低成本的路线。
二、核心代码实现
下面给出一个最小可运行的示例,展示如何采集Mock的ss流量并生成图片。代码中所有HTML特殊字符已转义,实际保存为.js文件即可执行。
const net = require('net');
const { createCanvas } = require('canvas');
const fs = require('fs');
// 存储流量记录:时间、长度、方向(1发送 0接收)
let records = [];
const server = net.createServer((socket) => {
socket.on('data', (buf) => {
records.push({
t: Date.now(),
len: buf.length,
dir: 1
});
// 模拟回包
const mock = Buffer.from('mock_ss_response');
socket.write(mock);
records.push({
t: Date.now(),
len: mock.length,
dir: 0
});
});
});
server.listen(8388, () => {
console.log('mock ss server on 8388');
// 3秒后生成图
setTimeout(() => {
drawImage();
server.close();
}, 3000);
});
function drawImage() {
const canvas = createCanvas(800, 400);
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, 800, 400);
ctx.strokeStyle = '#000';
ctx.beginPath();
ctx.moveTo(50, 350);
ctx.lineTo(750, 350);
ctx.stroke();
// 简单画柱
records.forEach((r, i) => {
const x = 50 + i * 20;
const h = r.len * 5;
ctx.fillStyle = r.dir === 1 ? '#f00' : '#00f';
ctx.fillRect(x, 350 - h, 15, h);
});
const out = fs.createWriteStream('ss_traffic.png');
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => console.log('image saved'));
}
2.1 代码要点说明
上面代码中,net.createServer用来模拟ss服务端,任何连进来的数据都被记为发送方向,并立即回一段固定Mock包作为接收方向。records数组保留了最原始的流量元数据,后续的绘图函数只依赖这个数组,不直接碰socket。
drawImage函数使用canvas库在内存画布上作业,红色代表客户端发往代理的数据,蓝色代表返回内容。柱体高度乘以系数仅作示意,真实场景应根据最大包长做动态缩放,避免超出画布。
三、常见问题与优化
当流量密度很高时,上面的逐包画柱会重叠。此时应该先做时间窗聚合,比如每100毫秒合并一次总字节数,再画面积图。下面示范聚合逻辑。
function aggregate(records, windowMs) {
const map = {};
records.forEach(r => {
const key = Math.floor(r.t / windowMs);
if (!map[key]) map[key] = { send: 0, recv: 0 };
if (r.dir === 1) map[key].send += r.len;
else map[key].recv += r.len;
});
return Object.keys(map).sort().map(k => map[k]);
}
3.1 聚合后绘图优势
聚合能把几千条记录压缩成几十个点,图片更干净,也能明显看出流量突发时段。如果直接渲染原始包,图片横向会被拉得很长,看不出趋势。
此外,可以把windowMs作为参数暴露给命令行,用户按需调整粒度。配合node-cron还能每小时自动出一张全天流量图,长期存档。
四、协议标签与术语注意
在日志或界面上提到HTML标签时,应写成转义形式,例如用<input>表示输入框元素,而不是真的插入标签。函数调用如socket.write()属于普通方法调用,不要误写成<socket.write/>这类错误形式。
如果要在文档里突出某个代码术语,可用<code>net.createServer</code>这样的写法,标签本身不转义,内部内容转义即可。遵循这些规则能保证文章或工具输出在浏览器里不会被解析错乱。
五、小结
用Node.js实现ss Mock2Image的关键,是先明确它面向Mock数据而非真实加密流,再以采集、聚合、绘图三段式组织代码。借助canvas库,服务端自绘图片比依赖外部软件更可控,也更容易自动化。
只要把文中示例的端口和Mock内容换成你自己的测试用例,就能快速得到专属的流量图。后续还可加入TCP重传标记、按域名着色等扩展,让图片承载更多调试信息。
Node.jsss_Mock2Image流量可视化修改时间:2026-08-11 16:33:43