WebSocket教程
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,非常适合实时应用。本教程将详细介绍如何在 Express 应用中集成 WebSocket 功能。
目录
- WebSocket 简介
- 选择 WebSocket 库
- 基础集成:Express + ws
- 高级集成:Express + Socket.io
- 实时功能实现
- 身份验证与安全
- 性能优化与扩展
- 部署注意事项
- 常见问题解决
WebSocket 简介
为什么需要 WebSocket?
- HTTP 是无状态的,每次请求都需要建立新连接
- HTTP 是单向的(客户端请求,服务端响应)
WebSocket 提供了:
- 持久化连接
- 双向实时通信
- 低延迟数据传输
适用场景
- 实时聊天应用
- 协作编辑工具
- 实时游戏
- 股票行情推送
- 在线拍卖/竞标系统
- 实时通知系统
选择 WebSocket 库
常用 Node.js WebSocket 库
ws - 轻量级 WebSocket 实现
- 优点:简单、高效
- 缺点:功能基础
Socket.io - 功能丰富的实时引擎
- 优点:自动重连、房间支持、回退机制
- 缺点:相对复杂、协议不标准
uWebSockets.js - 高性能实现
- 优点:极高性能
- 缺点:API 较复杂
本教程将重点介绍 ws
和 Socket.io
两种集成方式。
基础集成:Express + ws
安装 ws
npm install ws
基本集成示例
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
// 初始化 WebSocket 服务器
const wss = new WebSocket.Server({ server });
// Express 路由
app.get('/', (req, res) => {
res.send('Hello World!');
});
// WebSocket 连接处理
wss.on('connection', (ws) => {
console.log('New client connected');
// 接收消息
ws.on('message', (message) => {
console.log(`Received: ${message}`);
// 广播给所有客户端
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(`Server: ${message}`);
}
});
});
// 连接关闭
ws.on('close', () => {
console.log('Client disconnected');
});
});
server.listen(3000, () => {
console.log('Server started on http://localhost:3000');
});
客户端代码
<!DOCTYPE html>
<html>
<body>
<script>
const ws = new WebSocket('ws://localhost:3000');
ws.onopen = () => {
console.log('Connected to server');
ws.send('Hello Server!');
};
ws.onmessage = (event) => {
console.log('Message from server:', event.data);
};
ws.onclose = () => {
console.log('Disconnected from server');
};
</script>
</body>
</html>
运行 HTML
共享 Express 中间件
// 创建中间件验证函数
const verifyClient = (info, callback) => {
// 可以在这里访问 HTTP 请求信息
const token = info.req.headers['sec-websocket-protocol'];
if (isValidToken(token)) {
callback(true);
} else {
callback(false, 401, 'Unauthorized');
}
};
const wss = new WebSocket.Server({
server,
verifyClient
});
高级集成:Express + Socket.io
安装 Socket.io
npm install socket.io
基本集成示例
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// Express 路由
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
// Socket.io 连接处理
io.on('connection', (socket) => {
console.log('New client connected');
// 加入房间
socket.join('some room');
// 接收自定义事件
socket.on('chat message', (msg) => {
console.log('message: ' + msg);
// 广播给所有客户端
io.emit('chat message', msg);
// 或者广播给特定房间
io.to('some room').emit('room message', msg);
});
// 断开连接
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
server.listen(3000, () => {
console.log('Listening on *:3000');
});
客户端代码 (HTML)
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO Chat</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" />
<button>Send</button>
</form>
<script>
const socket = io();
// 接收消息
socket.on('chat message', (msg) => {
const li = document.createElement('li');
li.textContent = msg;
document.getElementById('messages').appendChild(li);
});
// 发送消息
document.getElementById('form').addEventListener('submit', (e) => {
e.preventDefault();
const input = document.getElementById('input');
socket.emit('chat message', input.value);
input.value = '';
});
</script>
</body>
</html>
运行 HTML
共享 Express 会话
const session = require('express-session');
const sharedSession = require('express-socket.io-session');
// 配置 Express 会话
const expressSession = session({
secret: 'my secret',
resave: true,
saveUninitialized: true
});
app.use(expressSession);
// 共享会话给 Socket.io
io.use(sharedSession(expressSession, {
autoSave: true
}));
// 在 Socket.io 中使用会话
io.on('connection', (socket) => {
const userId = socket.handshake.session.userId;
// ...
});
实时功能实现
1. 实时聊天应用
服务端:
io.on('connection', (socket) => {
// 用户加入聊天室
socket.on('join', (username) => {
socket.username = username;
socket.broadcast.emit('user joined', username);
});
// 处理聊天消息
socket.on('chat message', (msg) => {
io.emit('chat message', {
user: socket.username,
message: msg,
timestamp: new Date()
});
});
// 用户正在输入
socket.on('typing', () => {
socket.broadcast.emit('typing', socket.username);
});
});
客户端:
// 加入聊天
socket.emit('join', prompt('Enter your username'));
// 发送消息
input.addEventListener('keypress', () => {
socket.emit('typing');
});
form.addEventListener('submit', () => {
socket.emit('chat message', input.value);
});
2. 实时数据更新
服务端:
// 模拟实时数据更新
setInterval(() => {
const data = {
timestamp: new Date(),
value: Math.random() * 100
};
io.emit('data update', data);
}, 1000);
客户端:
socket.on('data update', (data) => {
updateChart(data); // 更新图表
});
3. 协作编辑
服务端:
let documentState = '';
io.on('connection', (socket) => {
// 发送当前文档状态给新用户
socket.emit('document update', documentState);
// 处理编辑操作
socket.on('edit', (operation) => {
// 应用操作到文档状态
documentState = applyOperation(documentState, operation);
// 广播给其他用户
socket.broadcast.emit('remote edit', operation);
});
});
身份验证与安全
1. JWT 认证
服务端:
const jwt = require('jsonwebtoken');
// Socket.io 认证中间件
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const decoded = jwt.verify(token, 'your-secret-key');
socket.user = decoded;
next();
} catch (err) {
next(new Error('Authentication error'));
}
});
io.on('connection', (socket) => {
console.log('Authenticated user:', socket.user.username);
});
客户端:
const token = localStorage.getItem('token');
const socket = io({
auth: {
token: token
}
});
2. 防止跨站 WebSocket 劫持 (CSWSH)
const wss = new WebSocket.Server({
verifyClient: (info, callback) => {
const origin = info.origin || info.req.headers.origin;
if (allowedOrigins.includes(origin)) {
callback(true);
} else {
callback(false, 403, 'Forbidden');
}
}
});
3. 消息验证
socket.on('message', (data) => {
if (typeof data !== 'string' || data.length > 1000) {
return socket.disconnect(true);
}
// 处理有效消息
});
性能优化与扩展
1. 多进程扩展
使用 cluster
模块和 Redis 适配器:
npm install @socket.io/redis-adapter redis
服务端:
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
const pubClient = createClient({ host: 'localhost', port: 6379 });
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
server.listen(3000);
}
2. 消息压缩
Socket.io 默认启用消息压缩:
const io = socketIo(server, {
perMessageDeflate: {
threshold: 1024 // 大于 1KB 的消息才压缩
}
});
3. 连接限制
const io = socketIo(server, {
maxHttpBufferSize: 1e8, // 最大消息大小 (100MB)
pingTimeout: 60000, // 60秒无响应断开
pingInterval: 25000 // 每25秒发送ping
});
部署注意事项
1. 反向代理配置 (Nginx)
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
2. SSL/TLS 配置
const fs = require('fs');
const https = require('https');
const server = https.createServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert')
}, app);
const io = socketIo(server);
3. 负载均衡
- 使用 Redis 适配器跨多服务器同步状态
- 确保粘性会话 (sticky sessions) 配置正确
常见问题解决
1. 连接失败
- 检查协议 (ws:// 或 wss://)
- 验证端口和路径是否正确
- 检查防火墙设置
2. 消息丢失
- 实现确认机制:
// 服务端
socket.emit('message', data, (ack) => {
console.log('Client acknowledged:', ack);
});
// 客户端
socket.on('message', (data, callback) => {
console.log(data);
callback('Received');
});
3. 内存泄漏
- 定期检查连接数
- 确保正确清理事件监听器
- 使用
socket.disconnect()
而不是socket.close()
4. 跨域问题
const io = socketIo(server, {
cors: {
origin: ["https://yourdomain.com", "https://anotherdomain.com"],
methods: ["GET", "POST"],
allowedHeaders: ["my-custom-header"],
credentials: true
}
});
总结
本教程详细介绍了 Express 与 WebSocket 的集成方法,包括:
- 基础集成:使用
ws
库实现简单 WebSocket 功能 - 高级集成:使用
Socket.io
实现功能丰富的实时应用 - 实时功能:聊天、数据更新、协作编辑等实现
- 安全措施:认证、授权和消息验证
- 性能优化:多进程扩展、消息压缩和连接限制
- 部署实践:反向代理、SSL 和负载均衡配置
WebSocket 为 Express 应用带来了实时通信能力,使你可以构建更加交互式和响应迅速的应用。根据你的项目需求选择合适的库:
- 需要简单轻量级方案 → 选择
ws
- 需要丰富功能和自动回退 → 选择
Socket.io
- 需要极致性能 → 考虑
uWebSockets.js
通过本教程的学习,你应该能够将 WebSocket 功能无缝集成到你的 Express 应用中,并构建出高效的实时应用程序。