理解WebSockets的核心逻辑
要学WebSockets,先得把它和熟悉的HTTP协议区分开——WebSockets是全双工、持久化的实时通信协议,而HTTP是“请求-响应”的单向短连接。举个直观的例子:HTTP像你给朋友发消息,发一条等一条回复;WebSockets像你们打视频电话,两边能同时说话,而且电话一直通着。

它的核心特点:
– 协议标识符:未加密用ws://
,加密(基于TLS)用wss://
(推荐生产环境用wss,更安全);
– 握手过程:客户端先发一个HTTP请求,头部带Upgrade: websocket
和Connection: Upgrade
,要求升级协议;服务器返回101 Switching Protocols
响应,完成握手后,连接就变成双向通道了;
– 数据传输:支持文本(UTF-8
)和二进制(Blob
/ArrayBuffer
),适合传输实时聊天消息、股票行情、监控数据等。
用表格对比更清楚:
| 特性 | HTTP | WebSockets |
|———————|———————–|———————-|
| 连接模式 | 请求-响应,单向短连接 | 全双工,持久化长连接 |
| 通信方向 | 客户端→服务器(或反向)| 客户端↔服务器同时传输 |
| 头部开销 | 每次请求带完整头部 | 初始握手后头部极小 |
| 适用场景 | 静态资源、普通接口 | 实时聊天、实时数据展示 |
从零搭建一个基础的WebSockets服务
最常用的后端实现是Node.js + ws库(ws是Node.js生态最成熟的WebSockets库,截至2025年8月,最新版本是8.17.1)。步骤超简单:
1. 初始化项目并安装依赖
打开终端,执行:
mkdir websocket-demo && cd websocket-demo
npm init -y
npm install ws # 安装ws库
2. 写服务器代码(server.js
)
const WebSocket = require('ws');
// 创建WebSockets服务器,监听8080端口
const wss = new WebSocket.Server({ port: 8080 });
// 当有客户端连接时触发
wss.on('connection', (ws) => {
console.log('新客户端连接');
// 给客户端发欢迎消息
ws.send('欢迎加入WebSockets实时通道!');
// 接收客户端消息
ws.on('message', (message) => {
console.log(`收到客户端消息:${message}`);
// 广播消息给所有在线客户端(比如群聊功能)
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) { // 确保连接是打开的
client.send(`[广播] ${message}`);
}
});
});
// 客户端断开连接时触发
ws.on('close', () => {
console.log('客户端断开连接');
});
});
console.log('WebSockets服务器启动,监听8080端口');
3. 启动服务器
node server.js
这样,一个基础的WebSockets服务就跑起来了!
前端如何连接与收发数据
前端不用装任何库,浏览器原生支持WebSocket
API。写个简单的HTML页面(index.html
):
<!DOCTYPE html>
<html>
<body>
<input type="text" id="messageInput" placeholder="输入消息">
<button onclick="sendMessage()">发送</button>
<div id="messageList"></div>
<script>
// 连接服务器(注意协议和端口要和后端一致)
const socket = new WebSocket('ws://localhost:8080');
// 连接成功时触发
socket.addEventListener('open', () => {
addMessage('连接服务器成功');
});
// 接收服务器消息
socket.addEventListener('message', (event) => {
addMessage(`服务器:${event.data}`);
});
// 连接错误时触发
socket.addEventListener('error', (error) => {
addMessage(`错误:${error.message}`);
});
// 连接断开时触发
socket.addEventListener('close', (event) => {
addMessage(`连接断开(代码:${event.code},原因:${event.reason})`);
});
// 发送消息函数
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (message) {
socket.send(message);
addMessage(`我:${message}`);
input.value = '';
}
}
// 渲染消息到页面
function addMessage(text) {
const div = document.createElement('div');
div.textContent = text;
document.getElementById('messageList').appendChild(div);
}
</script>
</body>
</html>
打开这个HTML文件,输入消息点“发送”,就能和服务器互动了——你发的消息会广播给所有打开页面的人,像个简单的群聊工具~
性能优化的3个关键技巧
基础功能跑通后,要考虑生产环境的性能问题。这3个技巧能帮你解决90%的性能瓶颈:
1. 用“心跳机制”保持连接稳定
WebSockets连接可能因为网络波动(比如地铁里信号差)无声断开,双方都不知道。心跳机制就是定期发“ping/pong”消息,确认连接是否存活。
服务器端实现(修改server.js
)
wss.on('connection', (ws) => {
console.log('新客户端连接');
// 每10秒发一次心跳(ping)
const heartbeatInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'heartbeat', data: 'ping' }));
}
}, 10000);
// 接收客户端的pong响应
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'heartbeat' && data.data === 'pong') {
console.log('收到心跳响应');
}
});
// 断开连接时清除定时器
ws.on('close', () => {
clearInterval(heartbeatInterval);
console.log('客户端断开');
});
});
前端实现(修改index.html
的script
)
socket.addEventListener('open', () => {
addMessage('连接成功');
// 每10秒发一次pong回应
const heartbeatInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: 'heartbeat', data: 'pong' }));
}
}, 10000);
// 断开时清除定时器
socket.addEventListener('close', () => {
clearInterval(heartbeatInterval);
});
});
2. 用“消息压缩”减少带宽占用
如果传输的消息很大(比如实时监控的图片数据),可以用permessage-deflate
扩展压缩——ws库原生支持,只需配置一下:
修改server.js
的服务器初始化代码:
const wss = new WebSocket.Server({
port: 8080,
perMessageDeflate: {
zlibDeflateOptions: { level: 3 }, // 压缩级别(1-9,越高压缩率越高但越耗CPU)
threshold: 1024, // 消息超过1KB才压缩
clientNoContextTakeover: true, // 减少客户端内存占用
serverNoContextTakeover: true // 减少服务器内存占用
}
});
前端不用改任何代码,浏览器会自动处理压缩和解压。
3. 用“连接池”应对高并发
如果你的服务要支持几千个并发连接,单进程Node.js可能扛不住——用PM2集群模式把连接分摊到多个进程:
先安装PM2:
npm install pm2 -g
然后创建ecosystem.config.js
配置文件:
module.exports = {
apps: [
{
name: 'websocket-server',
script: 'server.js',
instances: 'max', // 启动尽可能多的进程(等于CPU核心数)
exec_mode: 'cluster' // 集群模式
}
]
};
最后用PM2启动:
pm2 start ecosystem.config.js
这样每个CPU核心跑一个进程,能大幅提升并发能力。
常见问题的排查与解决
踩过坑才叫真正学会,这些问题90%的开发者都遇到过:
1. 跨域问题怎么解决?
WebSockets的握手阶段是HTTP请求,所以跨域问题和HTTP一样——服务器要允许客户端域名访问。解决方法有两种:
方法1:用Express配合ws(推荐)
先安装express:
npm install express
然后修改server.js
:
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// 处理OPTIONS请求(跨域预检)
app.options('*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有域名,生产环境换成具体域名
res.setHeader('Access-Control-Allow-Headers', 'Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version');
res.sendStatus(200);
});
// 启动服务器(注意用server.listen,不是wss.listen)
server.listen(8080, () => {
console.log('服务器启动在8080端口');
});
2. 连接断开后怎么自动重连?
前端加个重连逻辑就行,简单又好用:
function connect() {
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', () => {
addMessage('重连成功');
});
socket.addEventListener('close', () => {
addMessage('连接断开,3秒后重连...');
setTimeout(connect, 3000); // 3秒后重试
});
return socket;
}
// 初始化连接
const socket = connect();
3. 消息丢失怎么办?
实时系统最怕消息丢了——加“确认机制”:客户端发消息后,服务器必须回复“已收到”,没收到就重发。
客户端发送函数(修改index.html
)
function sendMessageWithAck(message) {
return new Promise((resolve, reject) => {
const messageId = Date.now(); // 给每条消息加唯一ID
const timeout = setTimeout(() => {
reject(new Error('消息发送超时'));
}, 5000); // 5秒超时
// 发送带ID的消息
socket.send(JSON.stringify({
id: messageId,
type: 'message',
data: message
}));
// 监听服务器的确认响应
socket.addEventListener('message', (event) => {
const res = JSON.parse(event.data);
if (res.id === messageId && res.type === 'ack') {
clearTimeout(timeout);
resolve(); // 确认成功
}
});
});
}
// 使用示例
async function send() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (message) {
try {
await sendMessageWithAck(message);
addMessage(`我:${message}`);
input.value = '';
} catch (err) {
addMessage(`发送失败:${err.message}`);
}
}
}
服务器端处理(修改server.js
)
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'message') {
console.log(`收到消息(ID: ${data.id}):${data.data}`);
// 回复确认
ws.send(JSON.stringify({
id: data.id,
type: 'ack'
}));
}
});
这样就算消息丢了,客户端也会重试,保证可靠性。
原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/272