安装方式
手动下载安装
下载 ZIP 后解压到技能目录即可安装。若在桌面客户端 WebView中直接下载出现异常,本站会改为提示页 + 原始链接,请按页内说明操作。
下载 ZIP (shub-websocket-reconnect-v1.0.0.zip)触发指令
/websocket-reconnect
跨平台安装指引
该技能声明兼容以下 1 个平台,将 ZIP 解压到对应目录即可被识别。
unzip shub-websocket-reconnect-v1.0.0.zip -d ~/.claude/skills/
mkdir -p 创建;启用 Skill 后请重启对应 Agent 让配置生效。
使用指南
WebSocket 断线重连
围绕 WebSocket 断线重连:重连退避、会话恢复与状态同步;与「websocket-hub-patterns」「websocket-engineer」可对照。 无需在每次任务前把零散英文说明手工拼进上下文,也 减少 与客户端默认行为脱节的试错;具体命令、钩子与 JSON 参数仍以 ZIP 包内 SKILL.md 为权威。下文结构与站内 MCP CLI 类专题稿相同:何时用、前置、流程、速查与故障。
何时使用
- 重连退避、会话恢复与状态同步
- 与「websocket-hub-patterns」「websocket-engineer」可对照
- 已获取本技能 ZIP,并准备在 Claude Code / OpenClaw 中按 SKILL.md 挂载。
- 希望用中文专题稿快速判断「该不该启用」,再深入英文 SKILL 查参数与边界。
- 需要与团队对齐同一套触发方式、目录约定或回调格式时。
前置条件
- 通用:可运行 Claude Code 或文档要求的客户端;有可读写的项目工作区(或 SKILL.md 指定的沙箱目录)。
- 权威细节:API Key / OAuth、钩子路径、环境变量以 ZIP 内 SKILL.md 为准。
典型流程
- 从 ClawHub / 站内分发获取技能 ZIP,校验版本与校验和(若提供)。
- 阅读 SKILL.md 的安装段落:目录落点、客户端类型(Claude Code / OpenClaw / 脚本)。
- 用文档中的最小示例完成第一次调用(单文件修改、单次查询或单次委派)。
- 确认工作目录、权限边界与输出路径后,再处理多文件或长耗时任务。
- 需要回调 / Webhook / 通知时,按 SKILL.md 配置端点并在测试环境先验通。
与 ZIP / SKILL.md 的关系
站内专题稿与 MCP CLI 类 oss 稿同样:概括何时用、怎么接、怎么排错;命令模板、钩子名、JSON 字段、版本矩阵一律以 ZIP 内 SKILL.md 与 ClawHub 上游为准。
命令示例(摘自包内 SKILL.md)
以下为从上游 SKILL.md(或入库正文)自动抽取的终端/脚本片段;路径、环境变量与参数以当前 ZIP 与官方说明为准。
ClawHub slug:websocket-reconnect(安装命令以 SKILL.md / claw CLI 为准)。
npm install ws
站内入库时的触发命令(完整语义见 ZIP):
# 使用本技能时可在对话中引用或执行上述指令;完整参数与示例见下载包内 SKILL.md。
/websocket-reconnect
最佳实践
- 先 SKILL.md 再猜参数;站内专题稿不替代 schema 与必填字段说明。
- 委派任务时写清验收标准(命令、文件路径、测试命令),减少来回追问。
- 长任务用文档推荐的回调 / 日志落盘代替高频轮询,省 Token 也省机器负载。
- 多技能同时启用时,注意钩子加载顺序与重复工具调用(以 SKILL.md 冲突说明为准)。
调试与排错
- 打开 stderr 与客户端日志;PTY/tmux 场景同时看面板最后几十行输出。
- 参数错误时对照 SKILL.md 中的 JSON/CLI 示例(引号、转义、工作目录)。
- 网络类失败:查代理、防火墙、MCP 传输方式(stdio / HTTP / SSE)。
速查
| 动作 | 说明 |
|------|------|
| 获取技能包 | ClawHub / 站内 ZIP,核对版本 |
| 权威步骤 | 优先阅读 ZIP 内 SKILL.md |
| 首次试跑 | 使用 SKILL.md 最小示例 |
| 验收 | 对照路径、测试命令或回调负载 |
常见故障
- 无输出或立即退出 → 工作目录错误、依赖未装、或 Claude Code 未登录;按 SKILL.md 自检清单执行。
- 权限被拒绝 → 检查沙箱路径、
--permission-mode与工具白名单。 - 与简介不符 → 以英文 SKILL 与上游仓库为准,站内稿仅作结构化导读。
# WebSocket Reconnect Skill
可靠的 WebSocket 连接管理,包含指数退避 + 抖动重连算法、心跳检测和断路器模式。
## 特性
- **指数退避重连**: 智能重试策略,延迟时间指数增长
- **随机抖动 (Jitter)**: 防止群震问题 (thundering herd)
- **心跳检测**: 自动检测僵死连接并触发重连
- **断路器模式**: 三次状态 (CLOSED, OPEN, HALF-OPEN) 防止级联故障
- **最大重试上限**: 避免无限重试消耗资源
- **事件驱动**: 完整的事件系统用于状态监控
## 安装依赖
```bash
npm install ws
```
## 快速开始
### 基础用法
```javascript
const { WebSocketReconnect } = require('./scripts/websocket-reconnect.js');
const ws = new WebSocketReconnect({
url: 'wss://api.example.com/socket',
maxRetries: 10,
baseDelay: 1000,
maxDelay: 30000
});
// 监听事件
ws.on('open', () => {
console.log('✅ Connected');
});
ws.on('message', (event) => {
console.log('📨 Message:', event.data);
});
ws.on('close', (event) => {
console.log('❌ Closed:', event.code, event.reason);
});
ws.on('error', (error) => {
console.error('⚠️ Error:', error.message);
});
ws.on('retry', (info) => {
console.log(`🔄 Retry ${info.attempt}/${info.maxRetries} in ${info.delay}ms`);
});
// 连接
ws.connect();
// 发送消息
ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
// 关闭连接
// ws.close();
```
### 完整配置示例
```javascript
const ws = new WebSocketReconnect({
// 连接配置
url: 'wss://api.example.com/socket',
protocols: ['graphql-ws'],
websocketOptions: {
headers: {
'Authorization': 'Bearer token123'
}
},
// 重连配置
maxRetries: 10, // 最大重试次数
baseDelay: 1000, // 基础延迟 (ms)
maxDelay: 30000, // 最大延迟 (ms)
multiplier: 2, // 退避倍数
jitter: 0.1, // 抖动系数 (0-1)
// 心跳配置
heartbeatInterval: 30000, // 心跳间隔 (ms)
heartbeatTimeout: 5000, // 心跳超时 (ms)
heartbeatMessage: JSON.stringify({ type: 'ping' }),
// 断路器配置
circuitBreaker: {
failureThreshold: 5, // 失败阈值
resetTimeout: 60000, // 重置超时 (ms)
halfOpenMaxRequests: 3 // 半开状态最大请求数
}
});
```
## 重连策略
### 指数退避计算
重连延迟按指数增长,避免频繁重试:
```
延迟 = baseDelay × (multiplier ^ 尝试次数)
```
示例 (baseDelay=1000ms, multiplier=2):
- 第 1 次重试:1000ms (1 秒)
- 第 2 次重试:2000ms (2 秒)
- 第 3 次重试:4000ms (4 秒)
- 第 4 次重试:8000ms (8 秒)
- ...
- 达到 maxDelay 后不再增长
### 抖动 (Jitter)
添加随机抖动防止多个客户端同时重试:
```javascript
// jitter = 0.1 时,延迟在 ±10% 范围内随机
实际延迟 = 计算延迟 × (0.9 ~ 1.1)
```
## 心跳检测机制
### 工作原理
1. 连接成功后,每隔 `heartbeatInterval` 发送 ping 消息
2. 等待服务器在 `heartbeatTimeout` 内回复 pong
3. 如果超时,认为连接已僵死,自动关闭并触发重连
### 服务器端要求
服务器需要响应 ping 消息:
```javascript
// 服务器示例 (Node.js + ws)
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const message = JSON.parse(data);
if (message.type === 'ping') {
ws.send(JSON.stringify({ type: 'pong' }));
}
// ... 处理其他消息
});
});
```
### 自定义心跳消息
如果服务器使用不同的心跳协议:
```javascript
const ws = new WebSocketReconnect({
url: 'wss://api.example.com',
heartbeatInterval: 30000,
heartbeatMessage: 'PING', // 字符串
// 或者
heartbeatMessage: { cmd: 'heartbeat' } // 对象 (会自动 JSON.stringify)
});
// 监听 pong 响应
ws.on('message', (event) => {
const data = JSON.parse(event.data);
if (data.type === 'pong') {
console.log('Heartbeat OK');
}
});
```
## 断路器模式
### 三种状态
**CLOSED (正常)**
- 请求正常通过
- 跟踪失败次数
- 失败达到阈值时打开
**OPEN (熔断)**
- 请求立即失败,不尝试连接
- 防止对故障服务的过载
- 经过 `resetTimeout` 后自动转为 HALF-OPEN
**HALF-OPEN (测试)**
- 允许有限数量的请求通过
- 成功则转为 CLOSED
- 失败则转回 OPEN
### 状态监控
```javascript
ws.on('circuitChange', (state) => {
console.log(`Circuit breaker state: ${state}`);
if (state === 'OPEN') {
console.log('⚠️ Service temporarily unavailable');
}
});
// 手动查询状态
const circuitState = ws.getCircuitState(); // 'CLOSED', 'OPEN', or 'HALF-OPEN'
```
### 手动控制断路器
```javascript
// 手动打开断路器
ws.openCircuit();
// 手动关闭断路器 (重置)
ws.closeCircuit();
```
## 事件参考
| 事件 | 参数 | 描述 |
|------|------|------|
| `open` | 无 | 连接成功建立 |
| `message` | `event` | 收到消息 (原生 MessageEvent) |
| `close` | `{code, reason}` | 连接关闭 |
| `error` | `Error` | 发生错误 |
| `retry` | `{attempt, delay, maxRetries}` | 即将重试 |
| `stateChange` | `state` | 连接状态变化 |
| `circuitChange` | `state` | 断路器状态变化 |
## 状态监控
### 获取统计信息
```javascript
const stats = ws.getStats();
console.log(stats);
// {
// state: 'OPEN',
// circuitState: 'CLOSED',
// retryCount: 2,
// maxRetries: 10,
// circuitFailures: 0,
// failureThreshold: 5,
// lastActivity: 1709532000000,
// uptime: 15000
// }
```
### 连接状态
- `CLOSED`: 未连接
- `CONNECTING`: 正在连接
- `OPEN`: 已连接
- `CLOSING`: 正在关闭
- `BLOCKED`: 被断路器阻止
- `FAILED`: 超过最大重试次数
## 完整示例
### 实时聊天客户端
```javascript
const { WebSocketReconnect } = require('./scripts/websocket-reconnect.js');
class ChatClient {
constructor(userId) {
this.userId = userId;
this.ws = new WebSocketReconnect({
url: `wss://chat.example.com/socket?user=${userId}`,
maxRetries: 10,
baseDelay: 1000,
maxDelay: 30000,
heartbeatInterval: 30000,
heartbeatTimeout: 5000,
circuitBreaker: {
failureThreshold: 5,
resetTimeout: 60000
}
});
this.setupEventHandlers();
}
setupEventHandlers() {
this.ws.on('open', () => {
console.log('✅ Connected to chat server');
// 发送上线通知
this.ws.send(JSON.stringify({
type: 'online',
userId: this.userId
}));
});
this.ws.on('message', (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
});
this.ws.on('close', () => {
console.log('❌ Disconnected from chat server');
});
this.ws.on('error', (error) => {
console.error('⚠️ Chat error:', error.message);
});
this.ws.on('retry', (info) => {
console.log(`🔄 Reconnecting... Attempt ${info.attempt}/${info.maxRetries}`);
});
this.ws.on('circuitChange', (state) => {
if (state === 'OPEN') {
console.log('⚠️ Chat service temporarily unavailable');
}
});
}
handleMessage(message) {
switch (message.type) {
case 'pong':
// 心跳响应,忽略
break;
case 'chat':
console.log(`[${message.from}]: ${message.content}`);
break;
case 'system':
console.log(`[System]: ${message.content}`);
break;
}
}
sendMessage(content) {
if (this.ws.state === 'OPEN') {
this.ws.send(JSON.stringify({
type: 'chat',
content: content
}));
} else {
console.log('⚠️ Cannot send message: not connected');
}
}
connect() {
return this.ws.connect();
}
disconnect() {
this.ws.close();
}
}
// 使用示例
const chat = new ChatClient('user123');
chat.connect();
chat.sendMessage('Hello, world!');
```
### GraphQL 订阅客户端
```javascript
const { WebSocketReconnect } = require('./scripts/websocket-reconnect.js');
class GraphQLSubscriptionClient {
constructor(endpoint, options = {}) {
this.ws = new WebSocketReconnect({
url: endpoint,
protocols: ['graphql-ws'],
maxRetries: options.maxRetries || 10,
heartbeatInterval: options.heartbeatInterval || 30000,
heartbeatMessage: JSON.stringify({ type: 'connection_init' }),
...options
});
this.subscriptions = new Map();
this.subscriptionId = 0;
this.setupEventHandlers();
}
setupEventHandlers() {
this.ws.on('open', () => {
console.log('✅ GraphQL WebSocket connected');
// 重新订阅所有订阅
this.resubscribeAll();
});
this.ws.on('message', (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
});
}
handleMessage(message) {
switch (message.type) {
case 'connection_ack':
console.log('GraphQL connection acknowledged');
break;
case 'next':
const subscription = this.subscriptions.get(message.id);
if (subscription && subscription.onData) {
subscription.onData(message.payload);
}
break;
case 'complete':
const sub = this.subscriptions.get(message.id);
if (sub && sub.onComplete) {
sub.onComplete();
}
this.subscriptions.delete(message.id);
break;
case 'error':
const s = this.subscriptions.get(message.id);
if (s && s.onError) {
s.onError(message.payload);
}
break;
}
}
subscribe(query, variables, callbacks) {
const id = String(++this.subscriptionId);
this.subscriptions.set(id, {
query,
variables,
onData: callbacks.onData,
onError: callbacks.onError,
onComplete: callbacks.onComplete
});
if (this.ws.state === 'OPEN') {
this.ws.send(JSON.stringify({
id,
type: 'subscribe',
payload: { query, variables }
}));
}
return () => this.unsubscribe(id);
}
unsubscribe(id) {
this.subscriptions.delete(id);
if (this.ws.state === 'OPEN') {
this.ws.send(JSON.stringify({
id,
type: 'complete'
}));
}
}
resubscribeAll() {
for (const [id, sub] of this.subscriptions) {
this.ws.send(JSON.stringify({
id,
type: 'subscribe',
payload: { query: sub.query, variables: sub.variables }
}));
}
}
connect() {
return this.ws.connect();
}
disconnect() {
this.ws.close();
}
}
```
## 配置选项
| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `url` | string | 必填 | WebSocket 服务器 URL |
| `protocols` | array | `[]` | 子协议列表 |
| `websocketOptions` | object | `{}` | WebSocket 构造函数选项 (Node.js) |
| `maxRetries` | number | `10` | 最大重试次数 |
| `baseDelay` | number | `1000` | 基础延迟 (毫秒) |
| `maxDelay` | number | `30000` | 最大延迟 (毫秒) |
| `multiplier` | number | `2` | 退避倍数 |
| `jitter` | number | `0.1` | 抖动系数 (0-1) |
| `heartbeatInterval` | number | `30000` | 心跳间隔 (毫秒) |
| `heartbeatTimeout` | number | `5000` | 心跳超时 (毫秒) |
| `heartbeatMessage` | string | `{"type":"ping"}` | 心跳消息 |
| `circuitBreaker.failureThreshold` | number | `5` | 打开断路器的失败次数 |
| `circuitBreaker.resetTimeout` | number | `60000` | 断路器重置超时 (毫秒) |
| `circuitBreaker.halfOpenMaxRequests` | number | `3` | 半开状态最大请求数 |
## 最佳实践
### 1. 合理设置重试次数
```javascript
// 关键业务:更多重试
maxRetries: 20, baseDelay: 2000
// 非关键业务:较少重试
maxRetries: 5, baseDelay: 1000
```
### 2. 心跳间隔选择
```javascript
// 高频交易:短间隔
heartbeatInterval: 10000, heartbeatTimeout: 3000
// 普通应用:标准间隔
heartbeatInterval: 30000, heartbeatTimeout: 5000
// 低带宽场景:长间隔
heartbeatInterval: 60000, heartbeatTimeout: 10000
```
### 3. 断路器阈值
```javascript
// 敏感服务:低阈值快速熔断
circuitBreaker: { failureThreshold: 3, resetTimeout: 30000 }
// 稳定服务:高阈值避免误熔断
circuitBreaker: { failureThreshold: 10, resetTimeout: 120000 }
```
### 4. 优雅关闭
```javascript
// 应用退出前
ws.close(1001, 'Client shutting down');
// 而不是直接
process.exit();
```
## 故障排查
### 问题:频繁重连
**原因**: 服务器不稳定或网络问题
**解决**:
1. 增加 `baseDelay` 和 `maxDelay`
2. 降低 `maxRetries`
3. 检查服务器日志
### 问题:心跳超时
**原因**: 服务器未响应 ping 或网络延迟高
**解决**:
1. 增加 `heartbeatTimeout`
2. 确认服务器正确响应 ping
3. 检查网络质量
### 问题:断路器持续 OPEN
**原因**: 服务持续故障
**解决**:
1. 检查服务健康状态
2. 增加 `failureThreshold`
3. 减少 `resetTimeout` 更快尝试恢复
## 性能影响
### 内存占用
每个实例约 1-2MB (包括事件处理器和定时器)
### CPU 占用
- 空闲连接:几乎为零 (仅心跳定时器)
- 重连期间:低 (指数退避减少重试频率)
### 网络流量
- 心跳:每 30 秒约 100 字节
- 重连:根据网络状况动态调整
## 浏览器兼容性
支持所有现代浏览器 (Chrome, Firefox, Safari, Edge) 和 Node.js 14+
## 许可证
MIT