从轮询到WebSocket:JS实现实时聊天与心跳保活详解
本文对比短轮询与长轮询的优劣,深入解析WebSocket基于HTTP Upgrade协议的双向通信原理,包括握手过程中的Sec-WebSocket-Key验证机制。通过Node.js服务端与前端JS客户端代码示例,演示如何实现纯文字单聊功能,并介绍利用ping/pong机制进行心跳保活以维持连接稳定性的最佳实践。
短轮询(Polling)
短轮询的核心机制是浏览器端按固定间隔(如数秒)向服务器发起 HTTP 请求。服务器接收到请求后,无论是否有新数据,都会立即返回响应并关闭 TCP 连接。其代码实现最为直接,通常利用 XHR 对象配合 setInterval 定时器定期向后端发送请求以获取最新数据。
setInterval(function() {
fetch(url).then((res) => {
// success code
})
}, 3000);
优点:实现简单。
缺点:会造成数据在一小段时间内不同步和大量无效的请求,安全性差、浪费资源。
长轮询(Long-Polling)
在长轮询模式下,客户端发送请求后,服务器不会立即返回数据,而是保持连接挂起(阻塞)。直到服务器有数据更新或连接超时,服务器才会返回响应。客户端收到响应后,会立即发起新的请求以建立新连接,如此循环往复以获取最新数据。大致效果如下:

客户端代码如下:
function async() {
fetch(url).then((res) => {
async();
// success code
}).catch(() => {
// 超时
async();
})
}
优点:比 Polling 做了优化,有较好的时效性。
缺点:保持连接挂起会消耗资源,服务器没有返回有效数据,程序超时。
WebSocket
前文提到的短轮询(Polling)和长轮询(Long-Polling)均属于客户端主动发起 Ajax 请求的通信模式,基于 HTTP 协议,服务器无法主动向客户端推送信息。
在体育赛事直播、聊天室、实时位置追踪等场景中,轮询方式显得低效且浪费资源,因为需要不断发送请求并维持连接。WebSocket 的出现解决了这一问题,它允许服务器主动向客户端发送信息,使浏览器具备了实时双向通信的能力。
未接触过 WebSocket 的人可能认为其技术深奥,但实际上它常用的 API 不多且易于掌握。在介绍具体用法之前,我们先了解其通信原理。
通信原理
当客户端尝试与服务器建立 WebSocket 连接时,握手过程始于客户端向服务端发送一个 HTTP 请求。该请求包含一个 Upgrade 请求头,明确告知服务端客户端希望建立 WebSocket 连接。
在客户端建立一个 WebSocket 连接非常简单:
let ws = new WebSocket('ws://localhost:9000');
类似于 HTTP 和 HTTPS,ws 协议对应也有 wss 协议用于建立安全连接,此处以 ws 为例。此时的请求头如下:
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: Upgrade // 表示该连接要升级协议
Cookie: _hjMinimizedPolls=358479; ts_uid=7852621249; CNZZDATA1259303436=1218855313-1548914234-%7C1564625892; csrfToken=DPb4RhmGQfPCZnYzUCCOOade; jsESSIONID=67376239124B4355F75F1FC87C059F8D; _hjid=3f7157b6-1aa0-4d5c-ab9a-45eab1e6941e; acw_tc=76b20ff415689655672128006e178b964c640d5a7952f7cb3c18ddf0064264
Host: localhost:9000
Origin: http://localhost:9000
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: 5fTJ1LTuh3RKjsJxydyifQ== // 与响应头 Sec-WebSocket-Accept 相对应
Sec-WebSocket-Version: 13 // 表示 websocket 协议的版本
Upgrade: websocket // 表示要升级到 websocket 协议
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/76.0.3809.132 Safari/537.36
响应头如下:
Connection: Upgrade
Sec-WebSocket-Accept: ZUip34t+bCjhkvxxwhmdEOyx9hE=
Upgrade: websocket

在响应行(General)中可以看到状态码 status code 是 101 Switching Protocols,这表示该连接已从 HTTP 协议转换为 WebSocket 通信协议。转换成功后,连接并未中断,而是建立了一个全双工通信通道,后续的消息发送和接收均通过此通道进行。
注意,请求头中包含一个 Sec-WebSocket-Key 字段,它与响应头中的 Sec-WebSocket-Accept 字段配套对应。其作用是提供基本防护,防止恶意或无效连接。Sec-WebSocket-Key 是客户端随机生成的 base64 编码字符串,服务器会利用该编码及一个固定算法进行处理:
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // 一个固定的字符串
accept = base64(sha1(key + GUID)); // key 就是 Sec-WebSocket-Key 值,accept 就是 Sec-WebSocket-Accept 值
其中 GUID 字符串是RFC6455官方定义的一个固定字符串,不得修改。
客户端收到服务端响应的 Sec-WebSocket-Accept 后,会使用之前生成的 Sec-WebSocket-Key 按照相同算法计算一次。若计算结果匹配,则握手成功。随后判断 HTTP Response 状态码是否为 101(切换协议),如果是,则连接建立成功。
实现简单单聊
接下来实现一个纯文字消息类型的一对一聊天(单聊)功能。直接参考以下代码,请注意阅读注释说明。
客户端:
function connectWebsocket() {
ws = new WebSocket('ws://localhost:9000');
// 监听连接成功
ws.onopen = () => {
console.log('连接服务端WebSocket成功');
ws.send(JSON.stringify(msgData)); // send 方法给服务端发送消息
};
// 监听服务端消息(接收消息)
ws.onmessage = (msg) => {
let message = JSON.parse(msg.data);
console.log('收到的消息:', message)
elUl.innerhtml += `<li>小秋:${message.content}</li>`;
};
// 监听连接失败
ws.onerror = () => {
console.log('连接失败,正在重连...');
connectWebsocket();
};
// 监听连接关闭
ws.onclose = () => {
console.log('连接关闭');
};
};
connectWebsocket();
从上述代码可见,WebSocket 实例的 API 直观易懂。通过 send() 方法可以发送消息,onmessage 事件用于接收消息,并对接收到的消息进行处理后显示在页面上。当 onerror 事件(监听连接失败)触发时,建议执行重连逻辑,以保持连接不中断。
服务端 Node: (这里使用ws库)
const path = require('path');
const express = require('express');
const app = express();
const server = require('http').Server(app);
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server: server });
wss.on('connection', (ws) => {
// 监听客户端发来的消息
ws.on('message', (message) => {
console.log(wss.clients.size);
let msgData = JSON.parse(message);
if (msgData.type === 'open') {
// 初始连接时标识会话
ws.sessionId = `${msgData.fromUserId}-${msgData.toUserId}`;
} else {
let sessionId = `${msgData.toUserId}-${msgData.fromUserId}`;
wss.clients.forEach(client => {
if (client.sessionId === sessionId) {
client.send(message); // 给对应的客户端连接发送消息
}
})
}
})
// 连接关闭
ws.on('close', () => {
console.log('连接关闭');
});
});
同理,服务端也有对应的发送和接收方法。完整示例代码见这里
这样浏览器和服务端就可以愉快的发送消息了,效果如下:

其中绿色箭头表示发出的消息,红色箭头表示收到的消息。
心跳保活
在实际使用 WebSocket 时,长时间无消息交互可能导致连接不稳定,这些未知情况引发的连接中断会影响客户端与服务端之间的通信。
为防止此类情况发生,可采用心跳保活机制:客户端像心跳一样每隔固定时间发送一次 ping 消息,告知服务器“我还活着”;服务器则返回 pong 消息,告知客户端“服务器还活着”。ping/pong 其实是一条与业务无关的假消息,也称为心跳包。
可以在连接成功之后,每隔一个固定时间发送心跳包,比如 60s:
setInterval(() => {
ws.send('这是一条心跳包消息');
}, 60000)
总结

通过上述介绍,大家对 WebSocket 应有了一定认识。它并不神秘:创建 WebSocket 实例时,会发送一个包含特殊字段 Upgrade 的 HTTP 请求,该连接随后由 HTTP 协议转换为 WebSocket 协议。这样客户端和服务端建立了全双工通信,通过 WebSocket 的 send 方法和 onmessage 事件即可通过这条通信连接交换信息。
以上就是如何用JS WebSocket实现简单聊天的详细内容,更多关于WebSocket的资料请关注本站其它相关文章!
NestJS 是什么?与 Express、Koa 的核心区别及选型建议
2026-11-05 13:46:41
生产环境升级 Node.js 的注意事项与兼容性测试策略
2026-11-05 09:19:24
nvm 常用命令详解:怎么平滑切换和升级 Node.js 版本
2026-11-04 09:02:34
JavaScript和JSP的根本区别:运行环境与执行机制解析
2026-11-03 23:30:18
使用nvm卸载指定版本Node.js并切换默认环境
2026-11-03 23:09:26
