 | |  |  |
- WSS协议示例 AIWROK 版websocket
复制代码 WSS协议示例 AIWROK 版websocket
- /**
- * WSS 协议示例 - AIWROK 版
- *
- * 使用 AIWROK 原生 websocket 对象实现 ws:// 和 wss://(TLS 加密)连接
- * 支持:双向通信、心跳保活、断线重连、JSON 消息收发
- *
- * AIWROK 原生 WebSocket API:
- * new websocket() - 创建实例
- * ws.setSocketID(String) - 设置唯一ID
- * ws.event(onOpen, onMsg, onErr, onClose) - 挂载四个回调
- * ws.connet(String) - 连接服务器(注意是双n)
- * ws.send(String) - 发送消息
- * ws.close() - 关闭连接
- *
- * ws:// 和 wss:// 的区别:
- * wss:// 走 TLS 加密通道,数据传输安全
- * AIWROK 的 websocket 对象会自动处理 TLS 握手
- *
- * 依赖:AIWROK 原生 websocket 对象,零依赖
- *
- * 使用方法:
- * 1. 修改 CFG.serverUrl 为你的 wss:// 地址
- * 2. AIWROK 中直接运行本脚本
- * 3. 日志窗口查看连接状态和收发消息
- */
- // ========== 配置 ==========
- var CFG = {
- // WSS 服务器地址(TLS 加密)
- // wss:// 走加密通道,ws:// 走明文通道
- serverUrl: "wss://echo.websocket.org/ws",
- // 心跳间隔(秒),定时发 PING 保持连接
- heartbeatIntervalSec: 15,
- // 重连配置
- reconnectIntervalMs: 3000, // 重连基础间隔(毫秒)
- maxReconnectAttempts: 999, // 最大重连次数(999=无限)
- reconnectMultiplier: 1.5, // 指数退避倍数
- maxReconnectDelayMs: 30000, // 最大重连延迟(毫秒)
- // 调试日志
- debug: true,
- // 脚本运行时长(毫秒),0=无限
- runDuration: 0
- };
- // ========== 全局状态 ==========
- var G = {
- ws: null, // websocket 实例
- connected: false, // 是否已连接
- reconnectCount: 0, // 已重连次数
- currentDelay: CFG.reconnectIntervalMs, // 当前重连延迟
- running: true, // 运行标志
- startTime: 0, // 启动时间
- msgSentCount: 0, // 已发送消息数
- msgRecvCount: 0, // 已接收消息数
- lastPongAt: 0, // 上次收到PONG时间
- heartbeatTimer: null // 心跳定时器
- };
- // ========== 时间工具 ==========
- function nowMs() {
- return (new Date()).getTime();
- }
- function nowSec() {
- return Math.floor(nowMs() / 1000);
- }
- // ========== 设备信息 ==========
- function getDeviceInfo() {
- var info = { imei: "", brand: "", model: "", screen: "" };
- try { info.imei = device.getIMEI() || ""; } catch (e) {}
- try { info.brand = device.getBrand() || ""; } catch (e) {}
- try { info.model = device.getModel() || ""; } catch (e) {}
- try { info.screen = screen.getScreenWidth() + "x" + screen.getScreenHeight(); } catch (e) {}
- return info;
- }
- // ========== 消息发送 ==========
- function sendMsg(data) {
- if (!G.connected || !G.ws) {
- printl("[WARN] 未连接,无法发送");
- return false;
- }
- try {
- var raw;
- if (typeof data === "string") {
- raw = data;
- } else {
- raw = JSON.stringify(data);
- }
- G.ws.send(raw);
- G.msgSentCount++;
- printl("[SEND] " + raw.slice(0, 200));
- return true;
- } catch (e) {
- printl("[ERROR] " + "发送异常: " + e.message);
- return false;
- }
- }
- // ========== 处理收到的消息 ==========
- function handleMessage(raw) {
- G.msgRecvCount++;
- printl("[RECV] " + raw.slice(0, 200));
- var obj = null;
- try {
- obj = JSON.parse(raw);
- } catch (e) {
- printl("[DEBUG] " + "非 JSON 消息: " + raw.slice(0, 100));
- return;
- }
- // 根据消息类型处理业务逻辑
- if (obj.type === "WELCOME") {
- printl("[INFO] " + "服务器欢迎: " + (obj.msg || ""));
- } else if (obj.type === "PING" || obj.type === "ping") {
- // 服务端心跳,回 PONG
- G.lastPongAt = nowMs();
- sendMsg({ type: "PONG", ts: nowSec() });
- } else if (obj.type === "PONG" || obj.type === "pong") {
- // 收到 PONG,更新心跳时间
- G.lastPongAt = nowMs();
- printl("[DEBUG] 收到 PONG");
- } else if (obj.type === "CMD_SCREENSHOT") {
- handleScreenshotCmd(obj);
- } else if (obj.type === "CMD_CLICK") {
- handleClickCmd(obj);
- } else if (obj.type === "CMD_STATUS") {
- handleStatusCmd(obj);
- } else {
- printl("[DEBUG] " + "未处理的消息类型: " + obj.type);
- }
- }
- // ========== 业务指令:截图 ==========
- function handleScreenshotCmd(obj) {
- try {
- var bitmap = screen.screenShotFull();
- if (!bitmap) {
- sendMsg({ type: "CMD_RESP", cmd: "CMD_SCREENSHOT", ok: false, msg: "截图返回空" });
- return;
- }
- var base64 = "" + bitmap.toBase64();
- try { bitmap.recycle(); } catch (e) {}
- sendMsg({
- type: "CMD_RESP",
- cmd: "CMD_SCREENSHOT",
- ok: true,
- cmdId: obj.cmdId,
- data: { base64: base64, length: base64.length }
- });
- } catch (e) {
- sendMsg({ type: "CMD_RESP", cmd: "CMD_SCREENSHOT", ok: false, msg: e.message });
- }
- }
- // ========== 业务指令:点击 ==========
- function handleClickCmd(obj) {
- try {
- var x = parseInt(obj.data.x, 10);
- var y = parseInt(obj.data.y, 10);
- if (isNaN(x) || isNaN(y)) {
- sendMsg({ type: "CMD_RESP", cmd: "CMD_CLICK", ok: false, msg: "坐标无效" });
- return;
- }
- if (typeof action !== "undefined") {
- action.click(x, y);
- } else if (typeof hid !== "undefined") {
- hid.click(x, y);
- }
- sendMsg({ type: "CMD_RESP", cmd: "CMD_CLICK", ok: true, cmdId: obj.cmdId });
- } catch (e) {
- sendMsg({ type: "CMD_RESP", cmd: "CMD_CLICK", ok: false, msg: e.message });
- }
- }
- // ========== 业务指令:状态查询 ==========
- function handleStatusCmd(obj) {
- var info = getDeviceInfo();
- info.type = "CMD_RESP";
- info.cmd = "CMD_STATUS";
- info.ok = true;
- info.cmdId = obj.cmdId;
- info.runtimeSec = nowSec() - G.startTime;
- info.msgSentCount = G.msgSentCount;
- info.msgRecvCount = G.msgRecvCount;
- info.reconnectCount = G.reconnectCount;
- sendMsg(info);
- }
- // ========== 心跳定时器 ==========
- function startHeartbeat() {
- stopHeartbeat();
- var hbMs = CFG.heartbeatIntervalSec * 1000;
- try {
- G.heartbeatTimer = setTimeout(function tick() {
- try {
- if (!G.running || !G.connected) return;
- // 检查心跳超时
- if (G.lastPongAt > 0 && (nowMs() - G.lastPongAt) > hbMs + 10000) {
- printl("[WARN] 心跳超时,主动断开重连");
- try { G.ws.close(); } catch (e) {}
- return;
- }
- sendMsg({ type: "PING", ts: nowSec() });
- G.heartbeatTimer = setTimeout(tick, hbMs);
- } catch (e) {
- printl("[ERROR] " + "心跳异常(已兜住): " + e.message);
- G.heartbeatTimer = setTimeout(tick, hbMs);
- }
- }, hbMs);
- } catch (e) {
- // setTimeout 不存在时用 runTime.setTimeout 兜底
- try {
- G.heartbeatTimer = runTime.setTimeout(function tick() {
- try {
- if (!G.running || !G.connected) return;
- if (G.lastPongAt > 0 && (nowMs() - G.lastPongAt) > hbMs + 10000) {
- printl("[WARN] 心跳超时,主动断开重连");
- try { G.ws.close(); } catch (e2) {}
- return;
- }
- sendMsg({ type: "PING", ts: nowSec() });
- G.heartbeatTimer = runTime.setTimeout(tick, hbMs);
- } catch (e3) {
- G.heartbeatTimer = runTime.setTimeout(tick, hbMs);
- }
- }, hbMs);
- } catch (e4) {}
- }
- }
- function stopHeartbeat() {
- if (G.heartbeatTimer) {
- try { clearTimeout(G.heartbeatTimer); } catch (e) {}
- try { runTime.stopTimeout(G.heartbeatTimer); } catch (e) {}
- G.heartbeatTimer = null;
- }
- }
- // ========== WebSocket 回调函数 ==========
- // 连接成功
- function wsOnConnected() {
- G.connected = true;
- G.reconnectCount = 0;
- G.currentDelay = CFG.reconnectIntervalMs;
- G.lastPongAt = nowMs();
- printl("[INFO] 连接成功 (onConnected) — TLS 握手完成");
- // 启动心跳
- startHeartbeat();
- // 连接成功后发送注册消息
- var info = getDeviceInfo();
- sendMsg({
- type: "REGISTER",
- device: info,
- ts: nowSec()
- });
- }
- // 收到文本消息
- function wsOnTextMessage(msg) {
- var raw;
- try { raw = String(msg); } catch (e) { raw = "" + msg; }
- handleMessage(raw);
- }
- // 连接错误
- function wsOnConnectError(err) {
- G.connected = false;
- var errMsg = "unknown";
- try { errMsg = String(err); } catch (e) {}
- if (!errMsg || errMsg === "null" || errMsg === "undefined") {
- errMsg = "连接错误(无详细信息)";
- }
- printl("[ERROR] " + "连接错误 (onConnectError): " + errMsg);
- stopHeartbeat();
- scheduleReconnect();
- }
- // 连接断开
- function wsOnDisconnected() {
- G.connected = false;
- printl("[WARN] 连接断开 (onDisconnected)");
- stopHeartbeat();
- scheduleReconnect();
- }
- // ========== 建立连接 ==========
- function connect() {
- // 先清理旧连接
- if (G.ws) {
- try { G.ws.close(); } catch (e) {}
- G.ws = null;
- }
- G.connected = false;
- printl("[INFO] 正在连接: " + CFG.serverUrl + " (重连 " + G.reconnectCount + "/" + CFG.maxReconnectAttempts + ")");
- // 创建 AIWROK 原生 websocket 实例
- var ws = null;
- try {
- ws = new websocket();
- } catch (e) {
- printl("[ERROR] " + "new websocket() 失败: " + e.message);
- scheduleReconnect();
- return;
- }
- G.ws = ws;
- // 设置 SocketID(唯一标识)
- try {
- ws.setSocketID("aiwrok_" + nowMs());
- } catch (e) {
- printl("[DEBUG] " + "setSocketID 失败(非致命): " + e.message);
- }
- // 挂载回调事件
- var eventOk = false;
- try {
- ws.event(wsOnConnected, wsOnTextMessage, wsOnConnectError, wsOnDisconnected);
- eventOk = true;
- printl("[DEBUG] ws.event() 挂事件成功");
- } catch (e1) {
- printl("[WARN] ws.event() 失败: " + e1.message + " — 尝试属性赋值");
- }
- // 如果 event() 不行,用属性赋值兜底
- if (!eventOk) {
- try {
- ws.onConnected = wsOnConnected;
- ws.onTextMessage = wsOnTextMessage;
- ws.onConnectError = wsOnConnectError;
- ws.onDisconnected = wsOnDisconnected;
- printl("[DEBUG] 属性赋值挂事件成功");
- } catch (e2) {
- printl("[ERROR] " + "两种挂事件方式都失败: " + e2.message);
- scheduleReconnect();
- return;
- }
- }
- // 发起连接(注意: connet 双n)
- try {
- ws.connet(CFG.serverUrl);
- printl("[DEBUG] connet() 已调用");
- } catch (e3) {
- printl("[WARN] connet() 异常: " + e3.message + " — 尝试 connect()");
- try {
- ws.connect(CFG.serverUrl);
- } catch (e4) {
- printl("[ERROR] " + "connect() 也失败: " + e4.message);
- scheduleReconnect();
- }
- }
- }
- // ========== 重连调度 ==========
- function scheduleReconnect() {
- if (!G.running) return;
- if (G.reconnectCount >= CFG.maxReconnectAttempts) {
- printl("[ERROR] 已达最大重连次数 " + CFG.maxReconnectAttempts + ",停止");
- return;
- }
- G.reconnectCount++;
- // 指数退避 + 随机抖动
- G.currentDelay = Math.min(
- G.currentDelay * CFG.reconnectMultiplier,
- CFG.maxReconnectDelayMs
- );
- var jitter = Math.floor(G.currentDelay * (0.8 + Math.random() * 0.4));
- printl("[INFO] 重连 " + G.reconnectCount + "/" + CFG.maxReconnectAttempts +
- " — " + (jitter / 1000).toFixed(1) + "s 后重试");
- try {
- setTimeout(function () {
- if (G.running) {
- try { connect(); } catch (e) {
- printl("[ERROR] " + "重连回调异常(已兜住): " + e.message);
- scheduleReconnect();
- }
- }
- }, jitter);
- } catch (e) {
- try {
- runTime.setTimeout(function () {
- if (G.running) {
- try { connect(); } catch (e2) {
- printl("[ERROR] " + "重连回调异常(已兜住): " + e2.message);
- scheduleReconnect();
- }
- }
- }, jitter);
- } catch (e3) {}
- }
- }
- // ========== 关闭清理 ==========
- function cleanup() {
- G.running = false;
- stopHeartbeat();
- if (G.ws) {
- try { G.ws.close(); } catch (e) {}
- G.ws = null;
- }
- G.connected = false;
- printl("[INFO] 资源已清理");
- }
- // ========== 主程序 ==========
- function main() {
- try {
- G.startTime = nowSec();
- printl("[INFO] ==========================================");
- printl("[INFO] WSS WebSocket 客户端 (AIWROK原生) 启动");
- printl("[INFO] " + " 服务器: " + CFG.serverUrl);
- printl("[INFO] 心跳间隔: " + CFG.heartbeatIntervalSec + "s");
- printl("[INFO] 最大重连: " + CFG.maxReconnectAttempts + " 次");
- printl("[INFO] " + " 设备信息: " + JSON.stringify(getDeviceInfo()));
- printl("[INFO] ==========================================");
- // 发起首次连接
- connect();
- // 主线程保活(链式 setTimeout,非阻塞)
- var tick = 0;
- (function keepAlive() {
- try {
- if (!G.running) {
- printl("[INFO] 主线程保活退出");
- return;
- }
- tick++;
- // 每 20 秒打印一次状态
- if (tick % 20 === 0) {
- printl("[INFO] " + "[保活] 已运行=" + (nowSec() - G.startTime) +
- "s 连接=" + G.connected +
- " 发送=" + G.msgSentCount +
- " 接收=" + G.msgRecvCount +
- " 重连=" + G.reconnectCount);
- }
- } catch (e) {
- printl("[ERROR] " + "保活异常(已兜住): " + e.message);
- }
- try { setTimeout(keepAlive, 1000); }
- catch (e) {
- try { runTime.setTimeout(keepAlive, 1000); } catch (e2) {}
- }
- })();
- // 如果设置了运行时长,到时自动退出
- if (CFG.runDuration > 0) {
- sleep.millisecond(CFG.runDuration);
- cleanup();
- }
- // 否则无限运行(靠保活循环维持)
- } catch (e) {
- printl("[FATAL] " + "主程序异常: " + e.message + "\n" + (e.stack || ""));
- try { cleanup(); } catch (e2) {}
- }
- }
- // ========== 启动 ==========
- main();
复制代码
| |  | |  |
|