
简介WebSocket作为一种在单个TCP连接上进行全双工通信的网络协议是实现实时应用如在线游戏、聊天室的核心技术。其工作原理基于HTTP升级握手之后便建立起持久连接允许服务器主动向客户端推送数据极大提升了实时交互效率。在技术价值上WebSocket克服了HTTP轮询的低效为需要低延迟、高频率数据交换的场景提供了标准解决方案。Protocol BuffersProtobuf则是一种高效的二进制序列化工具常用于优化网络传输性能通过预定义的数据结构生成更小、更快的编码消息。结合Node.js的异步非阻塞特性开发者能够构建高性能、可扩展的网络应用。在应用场景中这种技术组合特别适合需要实时通信和高效数据序列化的领域例如多人在线游戏、物联网设备通信和实时数据仪表盘。本文聚焦于通过逆向分析经典社交游戏如QQ农场的通信协议深入探讨如何利用Node.js和Protobuf解析WebSocket二进制数据流从而开发出模拟真实用户操作的自动化管理脚本为网络协议分析和自动化工程实践提供具体案例。1. 项目缘起当怀旧农场遇上现代自动化不知道还有多少人记得那个半夜定闹钟起来偷菜的年代QQ农场这个曾经风靡一时的社交游戏承载了一代人的青春记忆。如今它以小程序的形式在QQ和微信平台“复活”操作界面更加便捷但核心的“种菜-收获-偷菜”玩法依旧。然而对于很多像我一样既想重温经典又苦于没有大把时间守在手机前的“佛系”玩家来说手动操作依然是个负担。浇水、除草、杀虫、收获、播种、偷菜……一套流程下来虽然单次耗时不多但一天需要重复多次久而久之就成了一种甜蜜的负担。正是在这种矛盾下一个想法诞生了能不能写一个脚本让它自动帮我打理农场这个想法并非简单的“按键精灵”式的屏幕模拟点击因为小程序运行在封闭的容器内直接操作UI不仅效率低下而且极易被检测。更优雅、更接近本质的方式是直接与游戏服务器“对话”。这就像我们不通过点击网页按钮而是直接发送HTTP请求来完成登录、发帖一样。对于实时性要求高的游戏这个通信协议往往是WebSocket。于是这个项目的核心目标变得清晰逆向分析QQ/微信小程序中经典农场游戏的WebSocket通信协议并基于Node.js技术栈构建一个能够模拟真实玩家、实现7x24小时全自动管理的“农场管家”。它需要能自动登录、自动完成所有日常任务、自动规划作物种植以最大化收益甚至能智能地“偷取”好友的成熟果实。这不仅仅是一个怀旧项目更是一次对现代Web逆向工程、协议分析和自动化脚本开发的综合实践。对于前端开发者、Node.js爱好者以及对网络协议感兴趣的朋友来说这里面涉及的技术点相当有嚼头。2. 技术选型与核心工具链为什么是Node.js Protocol Buffers在决定动手之前技术栈的选择至关重要。市面上自动化脚本的语言很多Python凭借其丰富的库生态如selenium,requests是常见选择。但我最终选择了Node.js主要基于以下几点考量2.1 选择Node.js的深层理由首先生态契合度。目标游戏是Web小程序其前端逻辑由JavaScript编写后端通信也基于Web标准WebSocket。使用Node.js可以无缝地使用浏览器开发者工具中捕获的JavaScript对象、函数甚至可以直接复用某些加密算法片段在合规范围内进行分析理解。ws库提供了强大且纯粹的WebSocket客户端支持与浏览器行为高度一致。其次异步IO与事件驱动。农场管理是一个典型的“事件响应”型任务服务器推送消息如果实成熟、被偷客户端响应并执行操作。Node.js天生的异步非阻塞模型非常适合处理大量并发的网络连接和事件比如同时监控多个好友农场的状态变更。再者开发效率与工具链。NPM上有海量的工具包。例如axios用于处理登录等HTTP请求node-schedule或cron用于定时任务调度puppeteer如果需要可用于辅助分析或处理一些难以逆向的环节。整个开发、调试、打包流程非常顺畅。2.2 协议分析利器WebSocket与Protocol Buffers项目的核心难点在于协议逆向。现代游戏尤其是腾讯系产品为了效率和安全性很少使用纯JSON over WebSocket。更常见的是一种二进制协议而Protocol Buffers (protobuf)正是其中的佼佼者。WebSocket它提供了全双工、低延迟的通信通道是实时游戏的理想选择。我们的脚本需要建立一个持久的WebSocket连接来接收服务器的推送指令和发送客户端的操作请求。Protocol Buffers这是一种由Google开发的、语言中立、平台中立、可扩展的序列化数据结构机制。它比XML、JSON更小、更快、更简单。服务器和客户端之间传输的是一串串紧凑的二进制数据而非人类可读的文本。逆向的关键就在于破解这些二进制数据流对应的.proto文件定义的数据结构。一旦我们知道了每个请求Request和响应Response的message格式就能构造出合法的数据包来模拟任何玩家操作。这比解析渲染后的UI要直接和高效无数倍。2.3 辅助工具集抓包工具这是一切的起点。在电脑上运行微信或QQ PC版使用Proxyman、Charles或Fiddler等工具配置代理抓取小程序发出的所有网络请求。重点关注wss://WebSocket Secure开头的连接。反编译工具对于小程序可以使用一些开源工具如wxappUnpacker尝试获取小程序的源代码包以便分析其网络请求构造、加密参数生成等前端逻辑。注意此操作仅用于学习交流目的需严格遵守相关平台用户协议不得用于破坏游戏公平性或商业用途。Node.js核心库ws: 创建和管理WebSocket连接。protobufjs: 或protobuf-ts/plugin。用于动态加载、编译我们逆向出来的.proto定义文件并用于编码序列化和解码反序列化二进制消息。axios: 处理登录所需的HTTP请求获取关键令牌如uin,skey,token等。node-schedule: 实现复杂的定时任务例如“每5分钟检查一次作物状态”、“每天凌晨3点批量播种”等。提示整个逆向和分析过程必须在合法的范围内进行仅限于个人学习和技术研究。脚本的运行不应干扰游戏服务器的正常运行不应获取非公开数据更不应进行任何形式的牟利或破坏游戏平衡的行为。3. 深度逆向拆解WebSocket通信协议的全过程这是整个项目中最具挑战性也最有趣的部分。我们的目标是搞清楚客户端与农场游戏服务器“说了什么”以及“怎么说的”。3.1 抓取与识别通信链路首先打开抓包工具并确保配置正确安装证书、设置代理。然后启动QQ或微信PC版打开经典农场小程序。此时抓包工具会捕获到大量请求。登录与初始化阶段你会先看到一系列HTTPS请求指向腾讯的登录接口和资源加载接口。这些请求负责建立会话获取关键的登录态凭证如uin用户标识、skey、token等。这些凭证是后续所有WebSocket通信的“门票”必须妥善保存。同时还会加载一个游戏配置列表里面可能包含WebSocket服务器的地址gateway_url。定位WebSocket连接在请求列表中寻找wss://开头的URL。通常农场游戏的WebSocket网关地址会包含gateway、ws、farm等关键字。建立连接时客户端会将之前获取的token等参数以Query String的形式附在URL上例如wss://farm.ws.qq.com/ws?tokenxxxuinxxx。监听数据流成功建立WebSocket连接后客户端和服务器开始频繁地交换二进制数据。抓包工具会将这些数据包显示为一串串十六进制Hex或Base64编码的乱码。这就是我们的主战场。3.2 解析二进制协议寻找Protobuf的蛛丝马迹面对二进制流我们如何判断它是Protobuf特征识别Protobuf编码的二进制数据没有固定的消息头或分隔符。但我们可以观察其模式相同类型的操作如“浇水”发出的数据包长度和开头部分字节可能非常相似。我们可以尝试将抓到的数据包保存为二进制文件用文本编辑器如VS Code with Hex Editor插件查看寻找可能存在的字段分隔模式或重复结构。对比分析执行两个不同的操作比如“给1号土地浇水”和“给2号土地浇水”。抓取对应的两个请求包进行二进制对比。你会发现绝大部分字节相同只有少数几个字节不同——这很可能就是代表“土地ID”的字段。这就是逆向Protobuf的突破口。利用已知信息有时小程序的源代码如果可获取中会包含一些协议相关的常量或结构提示。或者服务器在初始化时下发的配置文件中可能包含部分消息类型的编号messageType或cmd。3.3 构建.proto定义文件通过反复的对比、测试和猜测我们开始构建自己的.proto文件。这是一个试错的过程。假设我们通过分析推测出一个简单的“浇水请求”的数据结构可能如下// farm.proto syntax proto3”; // 声明使用proto3语法 package farm; // 包名非必须但建议有 // 定义浇水请求消息 message WateringRequest { uint32 cmd_id 1; // 命令ID例如 1001 代表浇水 uint64 sequence 2; // 序列号用于请求-响应匹配 uint32 farm_id 3; // 农场ID uint32 plot_index 4; // 土地索引0-based } // 定义浇水响应消息 message WateringResponse { uint32 cmd_id 1; uint64 sequence 2; // 对应请求的序列号 uint32 result_code 3; // 结果码0表示成功 uint32 new_state 4; // 浇水后土地的新状态 }如何确定字段编号1,2和类型uint32,uint64这需要结合二进制分析。Protobuf采用(field_number 3) | wire_type作为键tag然后跟着字段值。通过分析二进制中变动的部分并对照不同操作可以逐步推断出每个字段的含义。3.4 使用protobufjs进行编解码一旦有了一个初步的.proto文件我们就可以在Node.js中用它来解码抓到的数据包验证我们的猜测是否正确。// 示例使用 protobufjs 加载和编解码 const protobuf require(“protobufjs”); const root await protobuf.load(“farm.proto”); const WateringRequest root.lookupType(“farm.WateringRequest”); // 假设我们有一个抓包得到的二进制Buffer let capturedBuffer Buffer.from(‘...’, ‘hex’); try { // 解码 const decodedMessage WateringRequest.decode(capturedBuffer); console.log(‘Decoded request:’, JSON.stringify(decodedMessage, null, 2)); // 如果解码成功且字段值符合预期如plot_index是1说明.proto定义基本正确 // 然后我们可以修改字段值重新编码发送给服务器 decodedMessage.plot_index 2; // 改为给2号土地浇水 const newBuffer WateringRequest.encode(decodedMessage).finish(); // 通过WebSocket发送newBuffer } catch (err) { console.error(‘Decode failed:’, err); // 说明.proto定义有误需要调整 }通过这种“捕获-解码-分析-修改-编码-发送-验证”的循环我们可以逐步完善对所有关键操作登录、获取农场信息、播种、收获、偷菜、帮助好友等的协议定义。4. 脚本架构设计与核心模块实现在摸清通信协议之后就可以着手设计脚本的整体架构了。一个好的架构应该模块清晰、易于扩展和维护。4.1 项目目录结构规划qq-farm-bot/ ├── config/ # 配置文件 │ ├── default.json # 默认配置如服务器地址 │ └── user.json # 用户敏感信息如账号Token.gitignore ├── core/ # 核心协议层 │ ├── proto/ # .proto定义文件 │ │ └── farm.proto │ ├── pb-client.js # Protobuf编解码客户端 │ └── ws-client.js # WebSocket连接管理 ├── services/ # 业务逻辑服务 │ ├── auth.js # 登录认证服务 │ ├── farm-manager.js # 农场管理核心种、收、偷 │ ├── task-scheduler.js # 定时任务调度 │ └── friend-monitor.js # 好友农场监控 ├── tasks/ # 具体任务定义 │ ├── watering-task.js │ ├── harvesting-task.js │ └── stealing-task.js ├── utils/ # 工具函数 │ ├── logger.js # 日志工具 │ └── helpers.js # 通用辅助函数 ├── app.js # 主程序入口 └── package.json4.2 核心模块详解4.2.1 WebSocket客户端 (core/ws-client.js)这个模块负责建立、维护与游戏服务器的WebSocket连接并处理消息的收发。const WebSocket require(‘ws’); const EventEmitter require(‘events’); const logger require(‘../utils/logger’); class FarmWebSocketClient extends EventEmitter { constructor(gatewayUrl, token) { super(); this.gatewayUrl ${gatewayUrl}?token${token}; this.ws null; this.sequence 0; // 全局自增序列号用于匹配请求响应 this.pendingCallbacks new Map(); // 存储未完成的请求回调 } connect() { return new Promise((resolve, reject) { this.ws new WebSocket(this.gatewayUrl); this.ws.on(‘open’, () { logger.info(‘WebSocket连接已建立’); this.emit(‘connected’); resolve(); }); this.ws.on(‘message’, (data) { // data是Buffer交给协议层解码 this.emit(‘message’, data); }); this.ws.on(‘error’, (err) { logger.error(‘WebSocket错误:’, err); this.emit(‘error’, err); reject(err); }); this.ws.on(‘close’, (code, reason) { logger.warn(WebSocket连接关闭代码: ${code}, 原因: ${reason}); this.emit(‘disconnected’); }); }); } // 发送二进制消息并返回一个Promise等待响应 sendRequest(messageType, requestPayload) { const sequence this.sequence; // 1. 将请求payload与消息头包含messageType和sequence组装成完整请求包 // 2. 使用protobuf编码成Buffer const requestBuffer this.encodePacket(messageType, sequence, requestPayload); return new Promise((resolve, reject) { // 设置超时 const timeoutId setTimeout(() { this.pendingCallbacks.delete(sequence); reject(new Error(请求超时 (seq: ${sequence}, cmd: ${messageType}))); }, 10000); // 存储回调 this.pendingCallbacks.set(sequence, { resolve, reject, timeoutId }); // 发送 this.ws.send(requestBuffer); }); } // 处理服务器推送的消息非请求响应 handleServerPush(messageBuffer) { // 解码根据消息类型分发事件 const { messageType, data } this.decodePacket(messageBuffer); this.emit(push:${messageType}, data); } }4.2.2 Protobuf编解码客户端 (core/pb-client.js)这个模块是协议层的核心负责加载.proto文件并提供统一的编解码接口。const protobuf require(‘protobufjs’); const path require(‘path’); class ProtocolBufferClient { constructor() { this.root null; this.messageTypes {}; // 缓存消息类型对象 } async loadProto(protoPath) { this.root await protobuf.load(protoPath); // 预加载常用消息类型提高性能 this.messageTypes.WateringRequest this.root.lookupType(‘farm.WateringRequest’); this.messageTypes.WateringResponse this.root.lookupType(‘farm.WateringResponse’); // ... 加载其他消息类型 console.log(‘Protobuf定义加载完成’); } encode(messageName, payload) { const MessageType this.messageTypes[messageName]; if (!MessageType) { throw new Error(未知的消息类型: ${messageName}); } // 验证payload是否符合.proto定义 const errMsg MessageType.verify(payload); if (errMsg) throw new Error(消息验证失败: ${errMsg}); // 创建消息对象并编码 const message MessageType.create(payload); return MessageType.encode(message).finish(); // 返回Buffer } decode(messageName, buffer) { const MessageType this.messageTypes[messageName]; if (!MessageType) { // 尝试动态查找效率较低仅用于未知类型调试 const found this.root.lookup(messageName); if (found) { this.messageTypes[messageName] found; return found.decode(buffer); } throw new Error(未知的消息类型: ${messageName}); } return MessageType.decode(buffer); } }4.2.3 农场管理服务 (services/farm-manager.js)这是业务逻辑的集大成者它利用底层的WebSocket和Protobuf客户端实现具体的游戏操作。const logger require(‘../utils/logger’); class FarmManager { constructor(wsClient, pbClient) { this.wsClient wsClient; this.pbClient pbClient; this.myFarmInfo null; // 缓存自己的农场信息 } async initMyFarm() { // 发送获取农场信息的请求 const requestPayload { /* ... */ }; try { const responseBuffer await this.wsClient.sendRequest(‘GetFarmInfoRequest’, requestPayload); const farmInfo this.pbClient.decode(‘GetFarmInfoResponse’, responseBuffer); this.myFarmInfo farmInfo; logger.info(农场初始化成功拥有土地: ${farmInfo.plots.length}块); return farmInfo; } catch (error) { logger.error(‘获取农场信息失败:’, error); throw error; } } async waterPlot(plotIndex) { if (!this.myFarmInfo) await this.initMyFarm(); const plot this.myFarmInfo.plots[plotIndex]; if (!plot || plot.state ! ‘NEEDS_WATER’) { logger.debug(土地 ${plotIndex} 无需浇水); return; } const requestPayload { farm_id: this.myFarmInfo.id, plot_index: plotIndex }; const requestBuffer this.pbClient.encode(‘WateringRequest’, requestPayload); try { const responseBuffer await this.wsClient.sendRequest(1001, requestBuffer); // 假设1001是浇水命令ID const response this.pbClient.decode(‘WateringResponse’, responseBuffer); if (response.result_code 0) { logger.info(成功为土地 ${plotIndex} 浇水); // 更新本地缓存状态 this.myFarmInfo.plots[plotIndex].state ‘GROWING’; } else { logger.warn(浇水失败结果码: ${response.result_code}); } } catch (error) { logger.error(为土地 ${plotIndex} 浇水时发生错误:, error); } } async harvestAll() { // 遍历所有土地检查状态是否为‘MATURE’是则发送收获请求 for (let i 0; i this.myFarmInfo.plots.length; i) { if (this.myFarmInfo.plots[i].state ‘MATURE’) { await this.harvestPlot(i); // 收获后可以立即播种实现自动化循环 await this.plantSeed(i, ‘WHEAT’); // 假设种植小麦 } } } // 智能偷菜获取好友列表检查其农场状态选择成熟度最高的果实偷取 async smartSteal() { const friends await this.getFriendList(); for (const friend of friends) { const friendFarm await this.getFriendFarmInfo(friend.uin); const ripePlots friendFarm.plots.filter(p p.state ‘MATURE’ !p.protected); if (ripePlots.length 0) { // 选择价值最高的作物偷取需要作物价值表 const targetPlot this.selectMostValuablePlot(ripePlots); await this.stealPlot(friend.uin, targetPlot.index); logger.info(从好友 ${friend.nickname} 的 ${targetPlot.index} 号土地偷取 ${targetPlot.cropName}); // 可以设置间隔避免操作过快 await this.sleep(1000); } } } }5. 实战部署、优化与风险规避脚本写好了如何让它稳定、可靠、安全地运行起来这里面有不少细节需要注意。5.1 环境配置与启动安装Node.js确保安装LTS版本的Node.js如18.x, 20.x。从官网下载安装包是最稳妥的方式。安装依赖在项目根目录运行npm install ws protobufjs axios node-schedule。配置信息将抓包获取到的token、uin、gateway_url等信息填入config/user.json此文件务必加入.gitignore。启动脚本运行node app.js。主程序app.js负责初始化所有模块登录认证建立连接并启动定时任务调度器。5.2 稳定性优化策略心跳机制WebSocket连接可能因网络波动或服务器策略而断开。需要在连接建立后定期如每30秒向服务器发送一个心跳包Ping并等待响应Pong。ws库内置了ping/pong机制可以方便地利用。// 在ws-client.js的connect方法中 this.ws.on(‘open’, () { // ... // 定期发送心跳 this.heartbeatInterval setInterval(() { if (this.ws.readyState WebSocket.OPEN) { this.ws.ping(); } }, 30000); }); this.ws.on(‘pong’, () { logger.debug(‘收到服务器pong响应’); });自动重连监听close和error事件一旦连接断开不是直接退出而是等待几秒后尝试重新连接并重新进行登录初始化流程。请求队列与速率限制不要一次性发送大量请求这容易被服务器识别为异常行为。实现一个简单的请求队列控制发送频率例如每秒不超过5个请求。对于“偷菜”这类操作更应在请求间加入随机延迟如1-3秒模拟真人操作。异常处理与日志所有网络请求、业务操作都必须用try...catch包裹记录详细的错误日志时间、操作、错误码、堆栈便于后期排查。日志可以输出到文件方便长期运行查看。5.3 安全与风险规避重中之重这是此类项目必须严肃对待的部分。合规性声明本项目及同类脚本应仅限于个人学习、研究网络协议和自动化技术之用。任何使用行为都需遵守《QQ/微信软件许可及服务协议》以及游戏自身的用户守则。避免滥用脚本的设计应遵循“辅助”而非“破坏”的原则。例如设置合理的操作频率避免24小时不间断、毫秒级响应的“机器人”式操作这会给服务器带来不必要的压力也明显违背了正常用户行为模式。账号风险使用自动化脚本存在账号被限制功能甚至封禁的风险。切勿在主账号、重要账号上使用。建议使用不常用的小号进行测试和研究。数据安全配置文件中的token等是账号的临时密钥等同于密码。务必妥善保管config/user.json不要上传到公开的代码仓库如GitHub。法律与道德边界绝对不要将脚本用于商业牟利如代挂服务。攻击游戏服务器。制作、传播外挂破坏游戏公平性。抓取、泄露其他用户的非公开数据。5.4 扩展与进阶思路一个基础的自动化脚本完成后还可以从以下方向深化策略引擎引入更复杂的种植策略。不是简单地种满同一种作物而是根据作物生长时间、成熟后的价值、被偷的风险结合好友的活跃时间段动态规划种植和收获时间实现经验或金币收益最大化。可视化监控使用WebSocket或Socket.IO为脚本增加一个简单的Web管理界面实时显示农场状态、任务执行日志、资源统计等。多账号管理设计一个轻量级的账号池管理模块支持多个小号轮流上线操作进一步降低单账号风险并可以实现“互助”用自己的小号给大号浇水、除草。容器化部署使用Docker将脚本及其Node.js环境打包可以更方便地在云服务器或本地NAS上部署实现真正的7x24小时运行。整个项目从逆向分析到稳定运行是一个充满挑战和乐趣的过程。它不仅仅是为了“收菜”更是对Web技术栈、网络协议、自动化工程的一次深度实践。每一次成功解码一个数据包模拟完成一个复杂操作都能带来巨大的成就感。当然时刻牢记技术的边界让工具服务于学习和效率的提升而非规则的破坏这才是开发者应有的态度。本文还有配套的精品资源点击获取