🔌 什么是WebSocket?
📖 定义
WebSocket是一种实现客户端和服务器之间双向实时通信的协议。与HTTP不同,一旦建立连接就会持续保持,服务器可以主动向客户端发送数据(push)。用于聊天、实时通知、游戏、协作工具等。
🎯 用比喻理解
电话 vs 邮件
HTTP (邮件)
├─ 寄信 (请求)
├─ 收到回信 (响应)
├─ 再写信 (请求)
└─ 每次都需要新信件 (开销)
WebSocket (电话)
├─ 一次接通电话
├─ 可以持续对话
├─ 双方都可以先说话
└─ 挂断前保持连接
快递 vs 直通管道
HTTP
┌─────────┐ 请求 ┌─────────┐
│ 客户端 │ ──────────→ │ 服务器 │
└─────────┘ └─────────┘
┌─────────┐ 响应 ┌─────────┐
│ 客户端 │ ←────────── │ 服务器 │
└─────────┘ └─────────┘
每次都是新连接!
WebSocket
┌─────────┐ ┌─────────┐
│ 客户端 │ ←─────────→ │ 服务器 │
└─────────┘ 持久连接 └─────────┘
↕ 双向
⚙️ 工作原理
1. WebSocket连接过程
1. HTTP握手请求
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
2. 服务器批准
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
3. WebSocket连接建立
↕ 开始双向通信
4. 数据传输
消息 ←→ 消息 ←→ 消息
5. 关闭连接
调用 close()
2. HTTP vs WebSocket
HTTP (单向, 请求-响应)
客户端 → 服务器: 请求
客户端 ← 服务器: 响应
[连接关闭]
WebSocket (双向, 持久)
客户端 ↔ 服务器
- 服务器可以主动发送消息
- 保持连接
- 实时通信
💡 实际例子
基本WebSocket (客户端)
// 在网页浏览器中
const ws = new WebSocket('ws://localhost:8080');
// 连接成功
ws.onopen = () => {
console.log('连接完成!');
ws.send('你好!'); // 发送消息
};
// 接收消息
ws.onmessage = (event) => {
console.log('收到消息:', event.data);
};
// 错误处理
ws.onerror = (error) => {
console.error('错误:', error);
};
// 连接 关闭
ws.onclose = () => {
console.log('连接关闭');
};
// 发送消息
ws.send('Hello Server!');
ws.send(JSON.stringify({ type: 'chat', message: 'Hi' }));
// 关闭连接
ws.close();
基本WebSocket服务器 (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
console.log('WebSocket服务器运行中: ws://localhost:8080');
// 客户端连接时
wss.on('connection', (ws) => {
console.log('客户端已连接!');
// 欢迎消息
ws.send('欢迎!');
// 接收消息
ws.on('message', (message) => {
console.log('收到消息:', message.toString());
// 向所有客户端广播
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
// 连接关闭
ws.on('close', () => {
console.log('客户端连接已关闭');
});
// 错误处理
ws.on('error', (error) => {
console.error('错误:', error);
});
});
Socket.io (实用聊天应用)
// ============ 服务器 (server.js) ============
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);
app.use(express.static('public'));
const users = new Map(); // 已连接用户
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
// 用户进入
socket.on('join', (username) => {
users.set(socket.id, username);
// 进入通知 (向所有人)
io.emit('user-joined', {
username,
userCount: users.size
});
console.log(`${username} 进入 (共 ${users.size} 人)`);
});
// 聊天消息
socket.on('chat-message', (message) => {
const username = users.get(socket.id);
// 向所有人广播
io.emit('chat-message', {
username,
message,
timestamp: new Date()
});
});
// 正在输入
socket.on('typing', () => {
const username = users.get(socket.id);
socket.broadcast.emit('user-typing', username);
});
// 连接关闭
socket.on('disconnect', () => {
const username = users.get(socket.id);
users.delete(socket.id);
// 退出通知
io.emit('user-left', {
username,
userCount: users.size
});
console.log(`${username} 退出 (剩余: ${users.size} 人)`);
});
});
server.listen(3000, () => {
console.log('服务器运行中: http://localhost:3000');
});
<!-- ============ 客户端 (public/index.html) ============ -->
<!DOCTYPE html>
<html>
<head>
<title>实时聊天</title>
<style>
#messages {
height: 300px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 10px;
}
.system {
color: #999;
font-style: italic;
}
</style>
</head>
<body>
<h1>实时聊天</h1>
<div id="messages"></div>
<div id="typing"></div>
<input id="message-input" type="text" placeholder="输入消息...">
<button id="send-btn">发送</button>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const typingDiv = document.getElementById('typing');
// 输入用户名
const username = prompt('请输入用户名:') || '匿名';
socket.emit('join', username);
// 用户进入通知
socket.on('user-joined', (data) => {
addMessage(`${data.username} 进入了。 (${data.userCount} 人在线)`, 'system');
});
// 用户退出通知
socket.on('user-left', (data) => {
addMessage(`${data.username} 退出了。 (${data.userCount} 人在线)`, 'system');
});
// 接收聊天消息
socket.on('chat-message', (data) => {
const time = new Date(data.timestamp).toLocaleTimeString();
addMessage(`[${time}] ${data.username}: ${data.message}`);
});
// 显示正在输入
socket.on('user-typing', (username) => {
typingDiv.textContent = `${username} 正在输入...`;
setTimeout(() => {
typingDiv.textContent = '';
}, 1000);
});
// 发送消息
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.emit('chat-message', message);
messageInput.value = '';
}
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
// 输入事件
let typingTimeout;
messageInput.addEventListener('input', () => {
clearTimeout(typingTimeout);
socket.emit('typing');
typingTimeout = setTimeout(() => {
// 停止输入
}, 500);
});
// 添加消息
function addMessage(text, className = '') {
const messageEl = document.createElement('div');
messageEl.className = `message ${className}`;
messageEl.textContent = text;
messagesDiv.appendChild(messageEl);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>
Room功能 (群聊)
// 服务器
io.on('connection', (socket) => {
// 进入房间
socket.on('join-room', (roomId) => {
socket.join(roomId);
console.log(`${socket.id} 进入了房间 ${roomId}`);
// 只向同房间的人发送
socket.to(roomId).emit('user-joined-room', {
userId: socket.id,
roomId
});
});
// 向特定房间发送消息
socket.on('room-message', ({ roomId, message }) => {
// 只向该房间发送
io.to(roomId).emit('room-message', {
userId: socket.id,
message
});
});
// 退出房间
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user-left-room', socket.id);
});
});
实时通知系统
// 服务器
const notifications = io.of('/notifications');
notifications.on('connection', (socket) => {
console.log('通知连接:', socket.id);
// 用户认证
const userId = socket.handshake.query.userId;
// 进入个人房间
socket.join(`user-${userId}`);
// 向特定用户发送通知
socket.on('send-notification', ({ targetUserId, notification }) => {
notifications.to(`user-${targetUserId}`).emit('notification', notification);
});
});
// 从任何地方发送通知
function sendNotification(userId, notification) {
notifications.to(`user-${userId}`).emit('notification', {
type: notification.type,
message: notification.message,
timestamp: new Date()
});
}
// 例: 新订单通知
app.post('/api/orders', async (req, res) => {
const order = await createOrder(req.body);
// 向卖家发送实时通知
sendNotification(order.sellerId, {
type: 'new-order',
message: `收到新订单!`,
orderId: order.id
});
res.json(order);
});
实时协作 (协同编辑)
// 服务器
io.on('connection', (socket) => {
socket.on('join-document', (docId) => {
socket.join(docId);
// 发送当前文档内容
const document = getDocument(docId);
socket.emit('document-loaded', document);
});
// 文档修改
socket.on('document-change', ({ docId, changes }) => {
// 更新文档
updateDocument(docId, changes);
// 向其他用户发送修改内容
socket.to(docId).emit('document-updated', {
userId: socket.id,
changes
});
});
// 共享光标位置
socket.on('cursor-move', ({ docId, position }) => {
socket.to(docId).emit('cursor-update', {
userId: socket.id,
position
});
});
});
🤔 常见问题
Q1. WebSocket vs HTTP轮询?
A:
// HTTP轮询 (低效)
// 客户端定期向服务器发送请求
setInterval(() => {
fetch('/api/messages')
.then(res => res.json())
.then(messages => {
// 检查新消息
});
}, 1000); // 每秒请求一次
// 问题:
// - 不必要的请求 (即使没有新消息也请求)
// - 延迟时间 ( 最多1秒)
// - 服务器负载
// WebSocket (高效)
// 实时双向通信
const socket = io();
socket.on('new-message', (message) => {
// 立即收到新消息!
});
// 优点:
// - 立即接收 (无延迟)
// - 服务器推送
// - 高效
Q2. WebSocket总是更好吗?
A:
// ✅ WebSocket适合的情况
1. 实时聊天
2. 实时通知
3. 协作工具 (协同编辑)
4. 实时游戏
5. 股票信息 (实时数据)
6. IoT设备控制
// ❌ WebSocket不需要的情况
1. 一般网站 (博客、新闻)
2. REST API (CRUD)
3. 静态内容
4. 不需要实时更新
// HTTP更好的情况:
// - 简单的请求-响应
// - 需要缓存
// - RESTful设计
Q3. Socket.io vs 原生WebSocket?
A:
// 原生WebSocket
const ws = new WebSocket('ws://localhost:8080');
ws.send('Hello');
// 优点: 轻量、快速
// 缺点: 没有额外功能、没有降级方案
// Socket.io
const socket = io();
socket.emit('message', 'Hello');
// 优点:
// 1. 自动重连
// 2. Room/Namespace 支持
// 3. 降级方案 (WebSocket不可用时使用Polling)
// 4. 基于事件
// 5. 二进制支持
// 缺点:
// 1. 体积大 (库大小)
// 2. 与原生WebSocket不兼容
// 选择标准:
// 简单项目 → 原生WebSocket
// 复杂项目 → Socket.io
Q4. WebSocket安全性?
A:
// 1. 使用 wss:// (WebSocket Secure)
// 就像 HTTP → HTTPS
// ws:// → wss://
const socket = new WebSocket('wss://example.com'); // ✅ 加密
const socket = new WebSocket('ws://example.com'); // ❌ 明文
// 2. 认证
// Socket.io
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
next();
} else {
next(new Error('认证失败'));
}
});
// 客户端
const socket = io({
auth: {
token: 'user-token'
}
});
// 3. 权限验证
socket.on('delete-message', (messageId) => {
const userId = socket.userId;
if (canDeleteMessage(userId, messageId)) {
deleteMessage(messageId);
} else {
socket.emit('error', '没有权限');
}
});
// 4. 速率限制
const rateLimit = require('socket.io-rate-limit');
io.use(rateLimit({
max: 10, // 最多10个
interval: 1000 // 每秒
}));
// 5. 输入验证
socket.on('message', (message) => {
// 防止XSS
const sanitized = sanitizeHtml(message);
if (sanitized.length > 1000) {
return socket.emit('error', '消息太长');
}
broadcast(sanitized);
});
Q5. WebSocket连接管理?
A:
// 1. 自动重连 (Socket.io)
const socket = io({
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
socket.on('disconnect', () => {
console.log('连接断开');
});
socket.on('reconnect', () => {
console.log('重连成功');
});
// 2. 心跳 (连接检查)
// 服务器
setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate(); // 无响应时关闭
}
ws.isAlive = false;
ws.ping(); // 发送ping
});
}, 30000);
ws.on('pong', () => {
ws.isAlive = true;
});
// 3. 内存管理
const MAX_CONNECTIONS = 1000;
wss.on('connection', (ws) => {
if (wss.clients.size > MAX_CONNECTIONS) {
ws.close(1008, '服务器已满');
return;
}
// 处理连接...
});
// 4. 优雅关闭
process.on('SIGTERM', () => {
console.log('服务器正在关闭...');
// 向所有连接发送关闭通知
wss.clients.forEach((ws) => {
ws.send(JSON.stringify({ type: 'server-shutdown' }));
ws.close();
});
wss.close(() => {
console.log('WebSocket服务器已关闭');
process.exit(0);
});
});
🎓 下一步
如果你理解了WebSocket,接下来可以学习:
- 什么是Node.js? - 构建WebSocket服务器
- 什么是React? - 实现实时UI
- 什么是Docker? (待创建文档) - 部署WebSocket服务器
实践练习
# 创建Socket.io聊天应用
# 1. 项目初始化
mkdir chat-app
cd chat-app
npm init -y
# 2. 安装包
npm install express socket.io
# 3. 创建服务器 (参考上面的例子)
# server.js
# 4. 运行
node server.js
# 5. 访问 http://localhost:3000
🎬 总结
WebSocket是实时Web的核心技术:
- 双向通信: 服务器 ↔ 客户端
- 持久连接: 一次连接,持续使用
- 实时: 即时数据传输
- 高效: 比轮询高效得多
如果需要实时功能,就使用WebSocket! 🔌✨