做实时功能做到现在,JavaScript连接WebSocket这一关,几乎每个前端工程师都要过。聊天消息、业务告警、行情刷新、远程控制台,只要数据需要从服务器主动推到浏览器,你就得new一个WebSocket实例,而不是让用户手动刷新页面去轮询。我最早是从轮询转过来的,踩了不少协议、机制和浏览器环境的坑。这篇文章围绕“JavaScript连接WebSocket”这个主题,先把连接背后的协议原理讲明白,再给出一套可以直接抄进生产项目的连接和封装方案,最后重点梳理那些让连接莫名其妙断掉、让页面卡死、让服务端收不到消息的真实问题。如果你是正在做前端实时功能、被WebSocket各种诡异报错困扰的开发者,这篇应该能帮你少走很多弯路。
1. 项目思路:为什么只推荐用WebSocket做实时双向通信
1.1 一个轮询翻车案例
我第一次认真考虑WebSocket,是在一个数据大屏项目里。需求本身不复杂:服务端每隔几秒推送一组最新的业务指标,前端不需要用户操作就能看到变化。当时为了赶进度,我直接用了setInterval加fetch,每5秒请求一次最新数据,前端确实能“动”起来。但上线当天就出了岔子,用户习惯同时开好几个标签页,每个标签页都独立跑着一个定时器向接口发起请求,高峰时接口每秒几十个请求,数据库连接池被打满,服务端日志里全是超时。
更尴尬的是,轮询根本不是“实时”。服务端数据在1秒时已经变化,客户端可能要到5秒后的下一次请求才能看到,中间隔了整整一个周期。如果缩短轮询周期,带宽和服务器压力又会成倍上涨。后来我把这块改成了JavaScript连接WebSocket的方式,服务端一旦有数据更新就直接推向页面,标签页数量对服务端的压力也不再是线性增长,这才算从根上把问题解决。
1.2 WebSocket解决的四个具体痛点
轮询只是其中一个反面例子。在日常开发里,WebSocket之所以被广泛使用,是因为它一次性解决了几个关键问题。
第一是主动推送。HTTP的本质是请求-响应模型,服务端在没有收到请求时不能主动把数据发给客户端。WebSocket建立连接后就变成了真正的全双工通道,服务端可以随时向客户端写数据,不需要客户端反复来问。
第二是低延迟。轮询的实时性取决于轮询间隔,而WebSocket的数据帧一旦到达就在毫秒级触发客户端回调,实时性有本质提升。
第三是省流量。HTTP轮询每次请求都要带上大量请求头、Cookie等信息,哪怕服务端没有新数据也要完整走一遍。WebSocket连接建立之后,后续消息都是轻量帧,头开销非常小。
第四是连接可复用。同一个WebSocket连接可以承载用户登录、业务消息、心跳检测、服务端控制指令等所有类型的实时数据,避免为不同功能建立多套HTTP连接。
1.3 同类方案对比,为什么是WebSocket而不是SSE
很多刚接触实时通信的读者会问,服务端推送用SSE(Server-Sent Events)不也可以吗?确实可以,但要区分场景。
| 对比项 | HTTP轮询 | SSE | WebSocket |
|---|---|---|---|
| 通信方向 | 客户端发起,服务端响应 | 服务端单向推送 | 全双工双向通信 |
| 实时性 | 取决于轮询间隔 | 高 | 高 |
| 浏览器自动重连 | 无 | 内置自动重连 | 需要自己实现 |
| 请求头开销 | 每次请求都很大 | 连接后较小 | 连接后很小 |
| 服务端实现难度 | 低 | 低 | 中高 |
| 适合场景 | 低频数据、接口兼容 | 服务端单向下发状态 | 聊天、协同、游戏、控制指令 |
SSE适合服务端单方向推送进度的场景,比如任务状态通知、日志流展示,它走的是普通HTTP,部署上更省心。但一旦客户端也需要主动向服务端发送高频数据,比如聊天消息、鼠标协同操作、遥控指令,SSE就不够用了。这时候JavaScript连接WebSocket就成了最直接的方案,因为它把上行和下行都打通了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 连接前必须吃透的协议机制与概念
2.1 一次WebSocket握手,客户端和服务端在做什么
很多人new了WebSocket之后,只关心能不能收到消息,却没有理解握手过程。结果一旦出问题,连排查方向都没有。
JavaScript里执行new WebSocket(url)时,浏览器会自动向服务端发送一个HTTP Upgrade请求,请求头大约长这样:
http复制GET /ws HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Origin: https://example.com
服务端收到这个请求后,如果确认支持WebSocket协议,会计算出一个Sec-WebSocket-Accept值并返回状态码101,表示协议切换成功。从这一刻开始,这条TCP连接不再按照HTTP规则收发数据,而是使用WebSocket的帧格式进行双向通信。
我在实际项目里排查问题时,第一步就是看服务端是否返回了101。如果返回的是200、400、403甚至502,那根本不是连接“断开”的问题,而是握手阶段就没成功,后端需要按普通HTTP请求来排查。
2.2 readyState连接状态机,写重连逻辑必须掌握
WebSocket实例里有一个readyState属性,很多开发者只在调试的时候看一眼,却没利用好它。理解这个状态机对写稳定连接特别重要。
javascript复制WebSocket.CONNECTING // 0,连接尚未建立
WebSocket.OPEN // 1,连接已建立,可以收发消息
WebSocket.CLOSING // 2,连接正在关闭
WebSocket.CLOSED // 3,连接已经关闭或根本无法建立
我通常在封装连接管理时,会用readyState判断当前连接是否可复用。比如用户点击“重连”按钮,如果发现readyState === WebSocket.OPEN,就不需要再重新创建一个连接;如果处于CLOSING状态,则等待close事件触发后再重连。这种判断能避免很多“重复创建连接导致消息风暴”的问题。
2.3 ws和wss的区别,以及URL里藏着的坑
WebSocket的地址有两种:ws://和wss://。它们的关系和HTTP与HTTPS的关系非常像。ws://走明文TCP,wss://走TLS加密传输,数据在传输过程中是加密的。
在浏览器环境里,有一个特别容易被忽略的规则:如果你的页面本身是用HTTPS协议打开的,那JavaScript里连接WebSocket时也必须使用wss://。如果使用ws://,浏览器会出于混合内容安全策略,直接拦截连接请求,控制台会报错,这是我在一个管理后台项目里亲眼见过的现象。反之,如果页面是HTTP环境,连wss://又可能因为证书不受信任而失败。所以自测阶段,页面协议和WebSocket协议必须保持一致。
另外,URL路径和查询参数是可以带的,比如wss://api.example.com/ws?roomId=123,服务端可以从中取出参数做业务处理。但我不建议把敏感信息比如登录token直接塞在URL里,因为URL很可能被服务端日志、网关日志记录下来,存在泄露风险。
2.4 同源限制和子协议,它们影响的是安全校验
WebSocket在建立连接时,浏览器会带上Origin头,服务端可以根据这个头判断请求来自哪个页面。但要注意,WebSocket本身不受浏览器同源策略限制,也就是一个页面里的JavaScript可以尝试连接任何服务器。所以服务端必须自行校验Origin,或者通过认证机制判断客户端是否有权限连接。这一点在实际项目里经常被忽略,导致接口被非预期来源的客户端随意调用。
至于子协议,对应的是请求头里的Sec-WebSocket-Protocol。在JavaScript里,可以通过new WebSocket(url, protocols)传入子协议名称。服务端只有在响应头里显式返回同一个子协议,浏览器才会认为连接建立成功,否则会直接握手失败。子协议一般用来协商数据格式,比如约定只使用JSON格式的业务消息,或者只使用特定的二进制协议。实际项目中如果服务端没有做子协议匹配,前端最好不要在第二个参数里传值,否则会给自己挖坑。
3. 从零开始,在JavaScript项目中稳定连接WebSocket
3.1 最小可用连接,先把事件理清
先给一份最基础的JavaScript连接WebSocket的代码,几乎每个项目刚开始都可以这样验证连通性。
javascript复制const socket = new WebSocket('wss://api.example.com/ws');
socket.addEventListener('open', () => {
console.log('连接建立成功');
socket.send(JSON.stringify({ type: 'auth', token: 'xxx' }));
});
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('收到服务端消息', data);
});
socket.addEventListener('close', (event) => {
console.log('连接关闭', event.code, event.reason);
});
socket.addEventListener('error', (event) => {
console.error('连接出现错误', event);
});
这里有个很容易忽视的点:错误事件触发后,连接通常紧接着会进入关闭状态,所以不要在error回调里直接做重连逻辑,而是应该统一在close回调里处理。为什么?因为在很多情况下浏览器并不会为每一个底层错误都单独触发error,但一定会触发close。只监听了error会导致重连逻辑不完整。
另一个经验是,尽量用addEventListener而不是直接给onopen、onmessage赋值。后者一旦被覆盖就会丢失之前的处理函数,特别是当多个模块都要监听同一个连接时,用事件监听方式管理会更安全。
3.2 让连接带着身份信息,我推荐的认证方案
真实项目里几乎不存在“谁都能连上来”的WebSocket服务,服务端一定要知道当前连接的是哪个用户。常见认证方案有几种。
第一种是把token放在URL查询参数里,比如wss://api.example.com/ws?token=xxx。这种方式简单直接,token可以随时用字符串拼接生成,服务端解析也容易。缺点是token会出现在网关和服务器访问日志里,日志一旦泄露,账号会面临风险。
第二种是用子协议携带信息,把token作为第二个参数传进去。但子协议本质上不是用来做认证的,浏览器对子协议有明确的长度和字符限制,所以这种方案也不是最理想。
第三种是连接建立成功后,客户端发送第一条鉴权消息,比如{ type: 'auth', token: 'xxx' },服务端校验通过后才允许客户端订阅其他业务消息。这种方案最大的好处是token不会出现在握手请求里,安全性更高。但服务端必须处理“未鉴权连接”的管理,比如超过一定时间没有发来鉴权消息就主动断开。
我个人的习惯是生产环境优先用第三种方案。前端先建连,再鉴权,鉴权有单独的超时和重试逻辑,跟业务消息完全分开。
3.3 像调用RPC一样使用WebSocket:回调封装思路
网上经常有人问“前端WebSocket怎么使用回调”,核心原因在于WebSocket是事件驱动的,发送消息和接收消息天然分离。业务代码如果直接裸写,经常会出现这样的局面:用户在列表页点击一个按钮,前端send了一条请求,但服务端返回的数据在另一个message事件里,你要靠消息内容里的某个字段去判断它对应哪一次请求。
解决方案很常见:给每条请求生成唯一ID,并用一个Map把ID和本次调用的resolve函数关联起来。当message事件收到返回时,通过消息里的ID找到对应的Promise并resolve。下面是一个简化的封装。
javascript复制class RpcSocket {
constructor(url) {
this.url = url;
this.ws = null;
this.messageId = 0;
this.pendingMap = new Map();
}
connect() {
return new Promise((resolve, reject) => {
const ws = new WebSocket(this.url);
ws.addEventListener('open', () => {
this.ws = ws;
resolve();
});
ws.addEventListener('message', (event) => this.handleMessage(event));
ws.addEventListener('error', reject);
});
}
handleMessage(event) {
const message = JSON.parse(event.data);
if (message.id && this.pendingMap.has(message.id)) {
const pending = this.pendingMap.get(message.id);
this.pendingMap.delete(message.id);
pending.resolve(message.data);
}
}
request(type, payload) {
return new Promise((resolve, reject) => {
const id = ++this.messageId;
const timer = setTimeout(() => {
this.pendingMap.delete(id);
reject(new Error('请求超时'));
}, 10000);
this.pendingMap.set(id, { resolve, reject, timer });
this.ws.send(JSON.stringify({ id, type, payload }));
});
}
}
实际使用时就变成了“发请求等结果”的直观模式:
javascript复制const client = new RpcSocket('wss://api.example.com/ws');
await client.connect();
const userInfo = await client.request('getUserInfo', { userId: 1001 });
在服务端支持的情况下,给每条消息带上递增ID是成本低收益高的方案,业务代码不需要再散落大量状态分支。这套思路在我的多个项目里都沿用下来,稳定性很不错。
3.4 断线重连与心跳,才是生产级连接的关键
如果只是写个Demo,断线后手动刷新页面也可以。但真正上线的项目里,网络波动、服务端重启、服务器发版都会导致连接断开。没有自动重连的WebSocket,用户会不断遇到“页面看着正常,但数据已经不再更新”的离线假象。
断线重连的代码本身不复杂,但必须考虑退避策略。如果所有人都用固定1秒重试,一旦服务端重启,成千上万个客户端同时发起重连,会把刚启动的服务端又一次打到崩溃。这种场景在真实生产里屡见不鲜。
我建议使用指数退避策略:
javascript复制function connectWithRetry(url, onMessage) {
let retryCount = 0;
let timer = null;
function scheduleReconnect() {
const delay = Math.min(30000, 1000 * Math.pow(2, retryCount));
retryCount += 1;
console.log('将在' + delay + '毫秒后重连');
timer = setTimeout(() => connect(), delay);
}
function connect() {
const ws = new WebSocket(url);
ws.addEventListener('open', () => {
retryCount = 0;
console.log('连接成功');
});
ws.addEventListener('message', (event) => onMessage(event.data));
ws.addEventListener('close', () => {
clearTimeout(timer);
scheduleReconnect();
});
}
connect();
}
指数退避不是“越等越久”这么简单。它的目的是让服务端在异常恢复后有喘息空间。我在实践中看到过很多次,服务端明明已经起来了,客户端还是不厌其烦地用几百毫秒间隔冲击连接,最后服务端日志全是TCP握手日志。加上重试次数上限和最大延迟,才能防止无限重试造成资源浪费。
心跳检测同样重要。WebSocket的连接如果长时间没有任何数据交互,很容易被中间的网络设备视为闲置连接而断开。浏览器提供的WebSocket API没有暴露直接发送Ping帧的方法,所以常规做法是使用业务层心跳,也就是定时发送一个JSON字符串,服务端收到后回复一个Pong消息。
javascript复制let heartbeatTimer = null;
let waitPongTimer = null;
function startHeartbeat(ws) {
clearInterval(heartbeatTimer);
clearTimeout(waitPongTimer);
heartbeatTimer = setInterval(() => {
ws.send(JSON.stringify({ type: 'ping', ts: Date.now() }));
waitPongTimer = setTimeout(() => {
console.warn('心跳超时,主动断开连接');
ws.close();
}, 5000);
}, 20000);
}
当收到服务端返回的Pong消息时,需要清掉那个超时定时器。如果连续多次没有收到Pong,就主动调用close(),让外层走断线重连逻辑。这样做比干等服务端断开要可靠得多。
3.5 文本、二进制和Blob,收到消息后先分清类型
不是所有服务端都会返回JSON字符串。在游戏、音视频、设备控制类项目里,服务端经常直接发送二进制数据。JavaScript连接WebSocket后,收到的消息类型取决于发送方和服务端的协商。在message事件中,有几种常见情况。
如果发送的是文本帧,event.data是字符串,可以直接JSON.parse。如果发送的是二进制帧,event.data默认可能是Blob,也可能是ArrayBuffer,这取决于binaryType属性。我习惯在连接建立后就把binaryType显式设置为'arraybuffer',因为处理ArrayBuffer的场景更灵活,可以用DataView解析不同的字段,也可以直接交给Web Worker解码。
javascript复制const ws = new WebSocket('wss://api.example.com/bin');
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', (event) => {
if (typeof event.data === 'string') {
// 文本帧,走JSON解析
} else if (event.data instanceof ArrayBuffer) {
const dv = new DataView(event.data);
// 按字段解析二进制内容
}
});
这个分类处理逻辑做到后期,我一般会单独抽一个handleRawMessage的方法,把“消息解析”和“业务处理”完全分开。这样后面加新消息类型,只需要增加一个解析分支,不会污染核心逻辑。
4. 实战中的异常现象与排查实录
4.1 关闭事件里的状态码,为什么要重视
每次WebSocket连接关闭时,close事件里都会携带一个code和reason。很多前端开发者只打印日志,没有仔细研究过状态码的含义。我这里整理几个最常见的。
| 关闭码 | 含义 | 常见场景 |
|---|---|---|
| 1000 | 正常关闭 | 客户端或服务端主动正常调用了close |
| 1001 | 正在离开 | 页面跳转、浏览器关闭、服务端重启 |
| 1006 | 异常关闭 | 网络断开、服务端进程崩溃、连接被中间设备切断 |
| 1008 | 策略违规 | 服务端拒绝本次连接,通常是鉴权失败或消息不合法 |
| 1011 | 服务端内部错误 | 服务端处理业务时抛出未捕获异常 |
如果看到1006,不要指望reason字段能给出更多信息,因为浏览器在遇到异常断开时往往拿不到服务端的关闭原因。此时排查重点不是前端,而是网络链路和服务端日志。如果看到1008,要立刻联想到是不是服务端校验token或消息格式不通过,服务端主动拒掉了连接。
4.2 “WebSocket导致浏览器崩溃”是什么原因
有人反馈页面打开一段时间后浏览器卡死甚至标签页无响应,第一反应是WebSocket不稳定。我在实际排查中遇到过几次,结论大多是消息数据处理逻辑写得有问题,而不是协议本身导致浏览器崩溃。
真正典型的原因是,服务端单次推送的数据量过大,或者推送频率过高,而前端在onmessage回调里又执行了耗时很长的同步操作。比如把几百条数据一次性解析后,直接循环创建了大量DOM节点插入页面。浏览器的UI线程一旦被长时间阻塞,就会表现为标签页卡死,最后被系统判定为无响应。
解决思路主要有三种。第一是对高频消息做合并和节流,不要每条消息都立刻更新UI,而是把数据存到缓冲区,用requestAnimationFrame或定时器按帧更新。第二是把重活放到Web Worker里,比如数据解析、文本压缩、格式转换,再通过postMessage把结果传回主线程。第三是后端配合,尽量避免单条消息体量过大,如果确实要推送大块数据,可以拆成多个分片消息,由前端做拼接。
我还有个顺手写的习惯:在onmessage回调开头记录一个时间戳,如果发现两次消息间隔低于50毫秒,就打印一条告警日志。这个告警能很快帮我们发现触发页面卡死的“高频消息源”,比事后看用户录屏要高效得多。
4.3 连接莫名断开,服务端却没有主动close
这类问题最让人头疼。现象是页面状态正常,服务端业务日志也没有异常,但客户端收到了1006。
大多数情况下,问题出在“网络链路把空闲连接断开了”。企业出口的网络设备、运营商网络、甚至云服务商的安全策略,都可能会将一定时间内没有数据传输的长连接视作无效连接并回收。要解决这个问题,唯一可靠的办法是保持心跳,让网络设备持续看到双向数据包,从而认为这条连接仍然处于活跃状态。
另一类常见原因是服务端前面有一层nginx或其他网关,网关默认的空闲超时时间比较短。客户端从不主动发消息,服务端也几乎没有上行数据,网关就会在空闲超时后把连接关掉。如果服务端日志里完全找不到主动关闭连接的记录,那么十有八九是链路中某个节点悄悄断开的。遇到这种情况,调整心跳间隔要比反复查服务端代码有效得多。
此外,服务端发版、重启、实例滚动更新也会导致连接断开。这不属于代码bug,但同样要求前端具备自动重连能力。如果连接断开后用户必须手动刷新才能恢复,那这个实时功能在生产环境里是不合格的。
4.4 握手阶段出现“stream disconnected before completion”类报错
不少开发者在浏览器Network里看到类似stream disconnected before completion: websocket closed by server before response的报错。这个报错的本质是,请求发出去之后,服务端在返回完整响应之前就直接关闭了连接。
我遇到过两种具体场景。第一种是前端把WebSocket地址当成普通HTTP地址来请求,比如用fetch去请求wss://api.example.com/ws,服务端当然没法按预期返回JSON,连接自然会在响应未完成时断开。第二种是服务端在握手过程中因为鉴权不通过、路径不正确或协议不支持,直接返回了一个标准HTTP错误响应然后关闭连接。浏览器只认101状态码,看到其他状态码就会认为握手失败。
排查这类问题,我的步骤比较固定。先打开Chrome DevTools的Network面板,找到这条WS请求,看它的HTTP状态码是101还是其他值。如果服务端返回的是200、400之类,说明后端根本没有走WebSocket升级逻辑,问题大概率不在前端。接着看服务端访问日志,确认这条连接是否到达了WebSocket处理层,以及具体的拒绝原因是什么。很多时候问题出在网关路径配置不匹配,比如前端连接/ws,网关却把请求转发到了不存在的业务接口上。
4.5 “谷歌浏览器高版本无法启用WebSocket”类问题怎么破
旧项目在浏览器升级之后突然连不上WebSocket,网上搜索时容易看到“浏览器禁用WebSocket”的说法。实际上,现代Chrome并没有提供禁用WebSocket的开关,也不存在需要去chrome://flags里手动开启WebSocket设置的选项。如果升级后连不上,要从下面几个方向排查。
首先看页面协议。HTTPS页面里连接明文ws://地址会被拦截,这可能是最直接的原因。然后看证书,如果服务端用的是自签名证书,并且浏览器没有信任该证书,wss://连接会在TLS握手阶段失败,控制台会提示证书错误。测试环境我建议直接用受信任的测试证书,或者在浏览器信任配置里主动导入,而不是在业务代码层面绕过。
其次检查端口。浏览器对部分端口有安全限制,虽然这不是WebSocket协议本身的要求,但使用特殊端口自测时很容易踩中。开发联调最好直接用80或443端口,避免把时间耗在端口策略上。
最后看是否有其它浏览器插件或安全软件干预了连接。我曾经遇到过一台测试机器上的安全软件拦截了wss://出站连接,现象就是Chrome控制台一直报网络错误,换一台机器却完全正常。这类环境问题偶尔出现,但排查时要讲证据,不要动不动就怀疑浏览器版本。
4.6 生产环境没有DevTools,怎么判断连接是否还活着
开发时我们可以打开DevTools看WS帧,但用户现场根本没有DevTools权限。为了线上排查方便,我建议在封装的连接模块里预留一个可视化状态输出。我的做法是维护一个最近N条消息的环形缓冲区,包括消息方向、类型、大小、时间戳,同时统计每分钟收到消息的条数。页面右上角放一个小图标,实时显示连接状态。
一旦用户反馈“页面好像不刷新了”,先看状态图标是绿色还是红色。如果连接还是OPEN状态但没有新消息,说明问题不是连接断了,而是服务端没有推数据或消息处理链路出错。如果连接已经CLOSED,那就会触发重连逻辑。这种区分在排障时非常节省时间。
4.7 常见问题速查表
| 现象 | 可能原因 | 推荐处理方式 |
|---|---|---|
| 握手失败,状态码不是101 | 服务端未实现WebSocket升级,路径错误,鉴权未过 | 先看Network面板状态码,再查服务端日志 |
| 连接短时间后断开 | 网关空闲超时,中间网络设备回收连接 | 增加业务层心跳,缩短心跳间隔 |
| 只在上线后断开,本地正常 | 服务端多实例部署重启,或网络环境差异 | 确认客户端有断线重连机制 |
| 收到1008状态码 | 服务端拒绝了token或消息格式不合法 | 检查认证流程,重新登录后再连 |
| 打开页面卡死 | onmessage里同步处理数据量过大 | 数据解析放Worker,UI更新节流 |
| HTTPS页面无法连接ws地址 | 混合内容安全策略拦截 | 改用wss地址 |
| 服务端重启后所有客户端同时重连导致雪崩 | 重连策略固定且无退避机制 | 使用指数退避和随机抖动 |
4.8 JavaScript连接WebSocket,不只是在浏览器里
最后想提一句,JavaScript连接WebSocket的应用场景其实比想象中广泛。除了页面,Electron桌面应用、Node.js服务端、类似OBS这种支持WebSocket插件的软件,使用的都是同一套连接思路。比如我做过一个小工具,用Node.js远程读取本地OBS的WebSocket接口,实现自动切换直播场景,原理就是向OBS启动的WebSocket服务端发起连接,然后按照插件定义的JSON协议发送控制指令。之前踩过的浏览器兼容坑,在Node环境里基本不涉及,但心跳、重连、消息ID匹配这些设计依然完全适用。
这说明WebSocket的连接经验是跨环境的。底层协议固定,JavaScript API在不同环境虽有差异,但事件驱动的核心模型不会变。把这一套原理和封装理解了,在浏览器或者Node环境里切换并不会有太高的学习成本。
我自己现在接新的WebSocket需求时,最先关注的已经不是“怎么连上”,而是“断开之后怎么恢复”。从轮询翻车,到踩过1006、握手失败、页面卡死这些坑,最大的体会是:WebSocket这种长连接,稳定性从来不是一次连接成功就完事,而是要在协议理解的基础上,把重连、心跳、超时、消息分包这些细节一点点补齐。希望这篇文章里记录的方案和排查思路,能帮你在JavaScript里连出一条稳的路。
