WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在实时应用中(如聊天、游戏、股票行情),WebSocket 是关键技术。本章将讲解 WebSocket 协议的基础,并在 Node.js 中使用 ws 库实现实时通信。
WebSocket 是一种网络传输协议,在 2011 年成为国际标准(RFC 6455)。它设计用于在客户端和服务器之间建立持久连接,双方可以随时开始发送数据。与 HTTP 不同,WebSocket 连接一旦建立,就一直保持,直到客户端或服务器主动关闭。这使得服务器可以主动向客户端推送消息,而不需要客户端频繁轮询。
WebSocket 协议使用 ws://(非加密)和 wss://(加密)作为 URI 方案。
| 特性 | HTTP | WebSocket |
|---|---|---|
| 通信模式 | 客户端主动请求,服务器响应 | 全双工,双方均可主动发送 |
| 连接生命周期 | 短连接,请求结束后断开 | 长连接,一直保持直到关闭 |
| 协议开销 | 每次请求都携带头部,开销大 | 建立连接后头部很小,适合高频通信 |
| 适用场景 | 请求-响应模式,如 REST API | 实时推送、聊天、游戏、股票行情 |
WebSocket 连接通过 HTTP 升级机制建立。客户端发送一个特殊的 HTTP 请求,带有 Upgrade: websocket 头,服务器如果支持,则返回 101 状态码(Switching Protocols),之后连接协议从 HTTP 切换到 WebSocket。
握手请求示例:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
服务器响应示例:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Node.js 生态中有多个 WebSocket 库,最常用的是 ws,它是一个轻量级、高性能的 WebSocket 实现,兼容浏览器。其他选择包括 socket.io(封装了 WebSocket 并提供降级方案)和 websocket(更完整的实现)。
本章使用 ws,因为它简单且性能好。安装:
npm install ws
创建一个 WebSocket 服务器很简单。可以与 HTTP 服务器共享端口,也可以独立运行。
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('新客户端已连接');
ws.on('message', (message) => {
console.log('收到消息: %s', message);
// 发送回执
ws.send(`服务器收到: ${message}`);
});
ws.on('close', () => {
console.log('客户端已断开');
});
ws.send('欢迎连接到 WebSocket 服务器');
});
console.log('WebSocket 服务器运行在 ws://localhost:8080');
许多应用需要同时提供 HTTP API 和 WebSocket,可以共享同一个端口:
const http = require('http');
const WebSocket = require('ws');
const express = require('express');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
app.get('/', (req, res) => {
res.send('Hello World');
});
wss.on('connection', (ws) => {
ws.on('message', (message) => {
console.log('received: %s', message);
});
ws.send('something');
});
server.listen(3000, () => {
console.log('HTTP + WebSocket 服务器运行在 http://localhost:3000');
});
浏览器原生支持 WebSocket,使用 WebSocket 构造函数。Node.js 中也可以使用 ws 库创建客户端。
const socket = new WebSocket('ws://localhost:8080');
socket.onopen = (event) => {
console.log('连接已打开');
socket.send('Hello Server!');
};
socket.onmessage = (event) => {
console.log('收到消息: ' + event.data);
};
socket.onclose = (event) => {
console.log('连接关闭');
};
socket.onerror = (error) => {
console.error('WebSocket 错误: ', error);
};
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080');
ws.on('open', () => {
console.log('连接成功');
ws.send('Hello Server!');
});
ws.on('message', (data) => {
console.log('收到消息: %s', data);
});
在聊天室等场景中,需要向所有连接或特定客户端发送消息。以下是一个广播示例:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
// 广播给所有客户端
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
});
wss.clients 是一个 Set,包含所有已连接的客户端。需要注意检查 readyState 确保连接还开着。
下面构建一个简单的聊天室,服务器广播所有消息,客户端在浏览器中显示。
const WebSocket = require('ws');
const http = require('http');
const express = require('express');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// 提供静态页面(可选)
app.use(express.static('public'));
wss.on('connection', (ws) => {
console.log('新用户加入');
// 广播加入消息
broadcast(JSON.stringify({ type: 'system', message: '新用户加入聊天室' }));
ws.on('message', (message) => {
// 将收到的消息广播给所有人
broadcast(JSON.stringify({ type: 'chat', message: message.toString() }), ws);
});
ws.on('close', () => {
console.log('用户离开');
broadcast(JSON.stringify({ type: 'system', message: '用户离开聊天室' }));
});
});
function broadcast(data, sender) {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
}
server.listen(3000, () => {
console.log('服务器启动在 http://localhost:3000');
});
<!DOCTYPE html>
<html>
<head>
<title>WebSocket 聊天室</title>
</head>
<body>
<h1>聊天室</h1>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="输入消息">
<button onclick="sendMessage()">发送</button>
<script>
const ws = new WebSocket('ws://localhost:3000');
const messagesDiv = document.getElementById('messages');
const input = document.getElementById('messageInput');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
const p = document.createElement('p');
if (data.type === 'system') {
p.style.color = 'gray';
p.textContent = data.message;
} else {
p.textContent = data.message;
}
messagesDiv.appendChild(p);
};
function sendMessage() {
const msg = input.value.trim();
if (msg) {
ws.send(msg);
input.value = '';
}
}
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
</script>
</body>
</html>
WebSocket 不仅可以发送文本,还可以发送二进制数据(如 ArrayBuffer、Buffer)。在 ws 中,如果接收到二进制数据,默认是 Buffer 对象。
ws.on('message', (data) => {
if (data instanceof Buffer) {
console.log('收到二进制数据,长度: %d', data.length);
} else {
console.log('收到文本: %s', data);
}
});
发送二进制:
const buffer = Buffer.from('hello', 'utf8');
ws.send(buffer);
由于网络不稳定,WebSocket 连接可能意外断开。为了及时发现死连接,通常需要实现心跳(ping-pong)机制。一些 WebSocket 服务器会自动处理,但也可以手动实现:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
function heartbeat() {
this.isAlive = true;
}
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', heartbeat);
});
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('close', () => {
clearInterval(interval);
});
connection 事件中验证。例如:const wss = new WebSocket.Server({ server });
wss.on('connection', (ws, req) => {
const token = new URL(req.url, 'http://localhost').searchParams.get('token');
if (!isValidToken(token)) {
ws.close(1008, 'unauthorized');
return;
}
// 继续处理
});
Origin 头,只允许特定源连接。socket.io 是一个封装了 WebSocket 的库,提供了自动重连、命名空间、房间、降级方案(轮询)等高级功能。如果你需要更丰富的特性,可以使用 socket.io。而 ws 更轻量,适合对性能和简单性有要求的场景。
WebSocket 是实现实时通信的强大工具。本章介绍了 WebSocket 协议基础、在 Node.js 中使用 ws 库创建服务器和客户端、广播消息、构建聊天室示例,以及安全考虑。掌握这些知识后,你可以构建实时应用如聊天系统、游戏、股票行情推送等。下一章我们将讨论 Node.js 的性能优化策略。