websocket连接需用状态机管理,定义initial、connecting、open、closing、closed、reconnecting六种状态及合法转移,封装状态变更、事件驱动、错误拦截与重连策略,避免原生readystate误判。

WebSocket 连接不是简单的“打开就用、断开就扔”,它有明确的状态流转:未建立 → 正在连接 → 已连接 → 关闭中 → 已关闭 → (可能重连)。直接靠 ws.readyState 判断容易出错(比如 `CONNECTING` 时发消息、`CLOSING` 时重复调用 close()),用状态机管理能让逻辑清晰、行为可控、错误可追溯。
定义明确的状态和合法转移
先梳理 WebSocket 原生状态(0-3)对应的实际语义,并补充业务需要的状态(如重连中、暂停中):
-
INITIAL:刚创建实例,尚未调用
new WebSocket() -
CONNECTING:已 new 实例,正在握手(对应
ws.readyState === 0) -
OPEN:连接成功,可收发(
=== 1) -
CLOSING:已调用
close(),等待对端确认(=== 2) -
CLOSED:连接彻底终止(
=== 3) - RECONNECTING:异常断开后,正尝试重建连接(非原生状态,需自行维护)
只允许合法转移,例如:INITIAL → CONNECTING、CONNECTING → OPEN / CLOSED / RECONNECTING、OPEN → CLOSING / CLOSED / RECONNECTING。非法转移(如从 CLOSED 直接发消息)应被拦截并报错。
用类封装状态 + 事件驱动流转
不依赖全局变量或散落的回调,把状态、转换逻辑、事件监听全收进一个类里:
class WebSocketStateMachine {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.state = 'INITIAL';
this.ws = null;
this.reconnectTimer = null;
this.maxReconnectAttempts = 5;
this.reconnectCount = 0;
}
<p>// 状态变更方法(带校验)
_transition(toState, reason = '') {
const validTransitions = {
INITIAL: ['CONNECTING'],
CONNECTING: ['OPEN', 'CLOSED', 'RECONNECTING'],
OPEN: ['CLOSING', 'CLOSED', 'RECONNECTING'],
CLOSING: ['CLOSED'],
CLOSED: ['INITIAL', 'CONNECTING'],
RECONNECTING: ['CONNECTING', 'CLOSED']
};</p><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">if (!validTransitions[this.state]?.includes(toState)) {
console.warn(`Invalid transition: ${this.state} → ${toState} (${reason})`);
return false;
}
this.state = toState;
this._emit('statechange', { from: this.state, to: toState, reason });
return true;}
connect() { if (this.state !== 'INITIAL' && this.state !== 'CLOSED') return; this._transition('CONNECTING', 'user call connect()');
this.ws = new WebSocket(this.url); this.ws.onopen = () => this._onOpen(); this.ws.onmessage = (e) => this._onMessage(e); this.ws.onclose = (e) => this._onClose(e); this.ws.onerror = (e) => this._onError(e);
}
_onOpen() { if (this.state === 'CONNECTING') { this._transition('OPEN', 'websocket onopen'); this.reconnectCount = 0; // 成功则重置计数 this._emit('open'); } }
_onClose(event) {
if (this.state === 'OPEN' || this.state === 'CLOSING') {
this._transition('CLOSED', code=${event.code} reason=${event.reason}
_onError() { if (this.state === 'CONNECTING') { this._transition('CLOSED', 'connection error'); this._maybeReconnect(); } }
send(data) { if (this.state !== 'OPEN') { console.warn('Cannot send: not in OPEN state', this.state); return false; } try { this.ws.send(data); return true; } catch (e) { console.error('Send failed:', e); this._transition('CLOSING', 'send error'); return false; } }
close(code = 1000, reason = '') { if (this.state === 'OPEN') { this._transition('CLOSING', 'user call close()'); this.ws.close(code, reason); } else if (this.state === 'CONNECTING') { this._transition('CLOSED', 'close during connecting'); this.ws?.close(); // 安全调用 } }
_maybeReconnect() {
if (this.reconnectCount >= this.maxReconnectAttempts) {
this._emit('reconnect_failed');
return;
}
this.reconnectCount++;
this._transition('RECONNECTING', attempt #${this.reconnectCount});
this.reconnectTimer = setTimeout(() => {
if (this.state === 'RECONNECTING') {
this._transition('CONNECTING', 'reconnect timeout');
this.connect();
}
}, Math.min(1000 * Math.pow(2, this.reconnectCount), 30000)); // 指数退避
}
// 简单事件系统(可用 EventEmitter 替代) _listeners = {}; _emit(event, ...args) { (this._listeners[event] || []).forEach(cb => cb(...args)); } on(event, cb) { if (!this._listeners[event]) this._listeners[event] = []; this._listeners[event].push(cb); } }
关键细节处理:避免常见陷阱
-
防止重复连接:在
connect()开头加状态守卫(if (this.state !== 'INITIAL' && this.state !== 'CLOSED') return),避免多次 new WebSocket -
清理副作用:进入
CLOSED或RECONNECTING时,清除定时器、取消未完成的请求、释放资源 -
区分主动关闭与被动断开:记录
isUserInitiated标志,决定是否自动重连;例如close()调用后设为true,而onclose中默认false -
消息队列缓冲:在
OPEN之前调用send()时,暂存消息数组;进入OPEN后批量发送(注意防爆) -
超时控制:给
CONNECTING状态加连接超时(如 10s 未触发onopen就走onerror流程)
配合业务层使用:解耦与可观测性
状态机本身不处理业务逻辑,只暴露状态事件。上层按需响应:
const ws = new WebSocketStateMachine('wss://api.example.com');
<p>ws.on('open', () => {
console.log('✅ Ready');
ws.send(JSON.stringify({ type: 'auth', token: 'xxx' }));
});</p><p>ws.on('close', (e) => {
console.log('⚠️ Closed:', e.code, e.reason);
});</p><p>ws.on('statechange', ({ from, to, reason }) => {
// 上报监控:如埋点、日志、UI 状态指示器更新
analytics.track('ws_state_change', { from, to, reason });
});</p><p>// 初始化连接
ws.connect();
</p>
这样业务代码只关心“发生了什么”,不操心“现在是什么状态”,也无需手动同步 UI 加载态、禁用按钮等——全部由状态变更事件驱动。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











