一、WebSocket 核心概念
1.1 什么是 WebSocket?
WebSocket 是一种全双工实时通信协议,允许客户端与服务器在单个 TCP 连接上进行双向数据传输。与 HTTP 的“请求-响应”模式不同,WebSocket 连接建立后,双方可随时主动发送数据,无需频繁建立连接,适合实时聊天、数据监控等场景。
- 协议标识:非加密连接用
ws://,加密连接用wss://(类似 HTTPS)。 - 与 HTTP 的关系:基于 HTTP 握手建立连接(通过
Upgrade头升级协议),之后脱离 HTTP 独立通信。 - 核心优势:低延迟、持久连接、双向通信、减少带宽消耗。
1.2 WebSocket 通信流程
- 握手阶段:客户端发送 HTTP 请求,携带
Upgrade: websocket头,请求升级协议。 - 连接建立:服务器响应
101 Switching Protocols,确认协议升级,TCP 连接保持打开。 - 双向通信:客户端与服务器通过帧(Frame)格式交换数据,直到某一方主动关闭连接。
- 连接关闭:通过
close帧终止连接,双方释放资源。
二、客户端 WebSocket 编程(JavaScript)
2.1 创建 WebSocket 连接
通过 WebSocket 构造函数初始化连接,参数为服务端 WebSocket 地址。
// 连接 WebSocket 服务器(ws 为非加密,wss 为加密)
const socket = new WebSocket('ws://localhost:8080/chat');
// 查看连接状态(readyState)
console.log('连接状态:', socket.readyState);
// 0: CONNECTING(连接中)
// 1: OPEN(已连接)
// 2: CLOSING(关闭中)
// 3: CLOSED(已关闭)
2.2 监听连接事件
WebSocket 提供 4 个核心事件,覆盖连接生命周期的所有状态:
// 1. 连接成功建立时触发
socket.onopen = () => {
console.log('连接已建立,可发送消息');
// 连接成功后才能发送数据
socket.send('客户端已上线');
};
// 2. 收到服务器消息时触发
socket.onmessage = (event) => {
// event.data 为消息内容(可能是字符串、Blob 或 ArrayBuffer)
console.log('收到消息:', event.data);
// 若消息为 JSON 格式,需解析
try {
const data = JSON.parse(event.data);
console.log('解析后的 JSON 消息:', data);
} catch (e) {
// 非 JSON 格式(如纯文本)
}
};
// 3. 连接发生错误时触发
socket.onerror = (error) => {
console.error('连接错误:', error);
};
// 4. 连接关闭时触发
socket.onclose = (event) => {
console.log(`连接关闭,状态码:${event.code},原因:${event.reason}`);
// 常见状态码:1000(正常关闭)、1001(端点离开)、1006(意外关闭)
};
2.3 发送与关闭连接
- 发送数据:使用
send()方法,支持字符串、JSON、二进制数据。 - 关闭连接:使用
close()方法,可指定状态码和原因。
// 发送字符串
socket.send('Hello, Server!');
// 发送 JSON 数据(需转为字符串)
const userMsg = { type: 'chat', content: '你好', sender: '张三' };
socket.send(JSON.stringify(userMsg));
// 发送二进制数据(如图片 Blob)
// const imageBlob = new Blob([imageData], { type: 'image/png' });
// socket.send(imageBlob);
// 主动关闭连接(状态码 1000 表示正常关闭)
socket.close(1000, '客户端主动结束会话');
三、服务端 WebSocket 编程(Node.js)
3.1 基于 ws 库的服务端实现
ws 是轻量、高性能的 WebSocket 库,严格遵循标准协议,适合对性能要求高的场景。
安装依赖
npm install ws
基础服务端代码
const WebSocket = require('ws');
// 创建 WebSocket 服务器,监听 8080 端口
const wss = new WebSocket.Server({ port: 8080 });
// 监听客户端连接事件(每个客户端连接会触发一次)
wss.on('connection', (ws) => {
console.log('新客户端已连接');
// 监听当前客户端发送的消息
ws.on('message', (data) => {
// data 默认为 Buffer 类型,需转为字符串
const message = data.toString();
console.log(`收到客户端消息:${message}`);
// 向当前客户端回复消息
ws.send(`服务端已收到:${message}`);
});
// 监听当前客户端断开连接
ws.on('close', () => {
console.log('客户端已断开连接');
});
// 监听错误
ws.on('error', (err) => {
console.error('连接错误:', err);
});
});
console.log('WebSocket 服务启动,地址:ws://localhost:8080');
3.2 基于 Socket.IO 库的服务端实现
Socket.IO 是功能更丰富的库,支持自动重连、房间管理、事件自定义等,适合复杂实时应用(需客户端也使用 Socket.IO)。
安装依赖
npm install socket.io
基础服务端代码
const http = require('http');
const { Server } = require('socket.io');
// 创建 HTTP 服务器(Socket.IO 依赖 HTTP 服务器)
const httpServer = http.createServer((req, res) => {
res.end('Socket.IO Server Running');
});
// 初始化 Socket.IO,允许跨域(开发环境)
const io = new Server(httpServer, {
cors: {
origin: "http://localhost:5500" // 客户端地址
}
});
// 监听客户端连接
io.on('connection', (socket) => {
console.log(`客户端 ${socket.id} 已连接`); // socket.id 为客户端唯一标识
// 监听客户端自定义事件(如 "chat")
socket.on('chat', (msg) => {
console.log(`收到消息:${msg}`);
// 向当前客户端回复
socket.emit('reply', `服务端已收到:${msg}`);
});
// 监听客户端断开
socket.on('disconnect', () => {
console.log(`客户端 ${socket.id} 已断开`);
});
});
// 启动服务器(端口 3000)
httpServer.listen(3000, () => {
console.log('Socket.IO 服务启动,地址:http://localhost:3000');
});
四、核心功能实现(服务端)
4.1 广播消息(发送给所有客户端)
向所有已连接的客户端推送消息(如群聊、公告)。
ws 库实现
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const message = data.toString();
// 遍历所有客户端,发送消息(排除自身)
wss.clients.forEach((client) => {
// 仅向状态为 "已连接" 的客户端发送
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(`其他用户说:${message}`);
}
});
});
});
Socket.IO 实现
// 接上一节 Socket.IO 服务端代码
io.on('connection', (socket) => {
socket.on('group_chat', (msg) => {
// 广播给所有客户端(包括发送者)
io.emit('group_message', `用户${socket.id}说:${msg}`);
});
});
4.2 单发消息(发送给指定客户端)
向特定客户端发送消息(如私聊、定向通知)。
ws 库实现(需维护客户端映射)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
// 存储客户端 ID 与连接的映射(key: 自定义 ID,value: ws 实例)
const clients = new Map();
let clientId = 1;
wss.on('connection', (ws) => {
// 为新客户端分配 ID
const id = clientId++;
clients.set(id, ws);
console.log(`客户端 ${id} 连接`);
// 接收消息时,解析目标 ID 并单发
ws.on('message', (data) => {
const { targetId, content } = JSON.parse(data.toString());
const targetClient = clients.get(targetId);
if (targetClient && targetClient.readyState === WebSocket.OPEN) {
targetClient.send(`来自客户端 ${id} 的私聊:${content}`);
}
});
// 断开连接时移除映射
ws.on('close', () => {
clients.delete(id);
console.log(`客户端 ${id} 断开`);
});
});
Socket.IO 实现(通过 socket.id 定位)
// 接上一节 Socket.IO 服务端代码
io.on('connection', (socket) => {
// 客户端发送私聊请求:{ targetId: 'xxx', content: '...' }
socket.on('private_chat', ({ targetId, content }) => {
// 向目标客户端发送消息
io.to(targetId).emit('private_message', {
from: socket.id,
content: content
});
});
});
4.3 房间管理(分组通信)
将客户端划分到“房间”,实现分组消息推送(如群聊房间、游戏房间)。
Socket.IO 实现(自带房间功能)
// 接上一节 Socket.IO 服务端代码
io.on('connection', (socket) => {
// 客户端加入房间
socket.on('join_room', (roomId) => {
socket.join(roomId); // 加入指定房间
console.log(`客户端 ${socket.id} 加入房间 ${roomId}`);
// 通知房间内所有成员
io.to(roomId).emit('room_notice', `用户 ${socket.id} 加入房间`);
});
// 向房间内发送消息
socket.on('room_chat', ({ roomId, content }) => {
// 仅向房间内成员发送(排除发送者可用 socket.to(roomId).emit())
io.to(roomId).emit('room_message', {
from: socket.id,
content: content
});
});
// 离开房间
socket.on('leave_room', (roomId) => {
socket.leave(roomId);
io.to(roomId).emit('room_notice', `用户 ${socket.id} 离开房间`);
});
});
五、实际案例:实时聊天系统(完整实现)
5.1 服务端(Socket.IO)
const http = require('http');
const { Server } = require('socket.io');
const httpServer = http.createServer();
const io = new Server(httpServer, {
cors: { origin: "http://localhost:5500" } // 允许客户端跨域
});
// 存储在线用户:{ socketId: 用户名 }
const onlineUsers = new Map();
io.on('connection', (socket) => {
console.log(`用户 ${socket.id} 连接`);
// 1. 用户登录(设置用户名)
socket.on('login', (username) => {
onlineUsers.set(socket.id, username);
// 广播系统消息:用户上线
io.emit('system_msg', `${username} 进入聊天室`);
// 同步在线用户列表
io.emit('online_users', Array.from(onlineUsers.values()));
});
// 2. 群聊消息
socket.on('send_msg', (content) => {
const username = onlineUsers.get(socket.id) || '匿名';
const msg = {
user: username,
content: content,
time: new Date().toLocaleTimeString()
};
// 广播给所有用户
io.emit('new_msg', msg);
});
// 3. 断开连接
socket.on('disconnect', () => {
const username = onlineUsers.get(socket.id);
if (username) {
onlineUsers.delete(socket.id);
io.emit('system_msg', `${username} 离开聊天室`);
io.emit('online_users', Array.from(onlineUsers.values()));
}
console.log(`用户 ${socket.id} 断开`);
});
});
httpServer.listen(3000, () => {
console.log('聊天服务启动:http://localhost:3000');
});
5.2 客户端(HTML + JavaScript)
<!DOCTYPE html>
<html>
<body>
<h3>实时聊天室</h3>
<div>
<input type="text" id="username" placeholder="请输入用户名">
<button onclick="login()">登录</button>
</div>
<div id="onlineUsers" style="margin: 10px 0; color: #666;">在线用户:无</div>
<div id="chatLog" style="border: 1px solid #ccc; height: 300px; overflow: auto; padding: 10px;"></div>
<div>
<input type="text" id="msgInput" placeholder="输入消息">
<button onclick="sendMsg()">发送</button>
</div>
<!-- 引入 Socket.IO 客户端库 -->
<script src="https://cdnjs.cloudflare.com/ajaxajax/libs/socket.io/4.5.1/socket.io.min.js"></script>
<script>
// 连接服务端
const socket = io('http://localhost:3000');
let isLogin = false;
// 登录
function login() {
const username = document.getElementById('username').value.trim();
if (username) {
socket.emit('login', username);
isLogin = true;
document.getElementById('username').disabled = true; // 禁用输入
}
}
// 发送消息
function sendMsg() {
if (!isLogin) {
alert('请先登录');
return;
}
const input = document.getElementById('msgInput');
const content = input.value.trim();
if (content) {
socket.emit('send_msg', content);
input.value = ''; // 清空输入
}
}
// 接收新消息
socket.on('new_msg', (msg) => {
const log = document.getElementById('chatLog');
log.innerHTML += `<div><strong>${msg.user}</strong> [${msg.time}]:${msg.content}</div>`;
log.scrollTop = log.scrollHeight; // 滚动到底部
});
// 接收系统消息(上下线通知)
socket.on('system_msg', (msg) => {
const log = document.getElementById('chatLog');
log.innerHTML += `<div style="color: #999;">${msg}</div>`;
log.scrollTop = log.scrollHeight;
});
// 更新在线用户列表
socket.on('online_users', (users) => {
document.getElementById('onlineUsers').textContent = `在线用户:${users.join(', ')}`;
});
</script>
</body>
</html>
六、WebSocket 重点难点与解决方案
6.1 断线重连
网络波动可能导致连接中断,需客户端实现自动重连机制。
// 客户端断线重连示例(原生 WebSocket)
function connectWebSocket() {
const socket = new WebSocket('ws://localhost:8080');
let reconnectTimer = null;
// 连接关闭时触发重连
socket.onclose = () => {
console.log('连接关闭,尝试重连...');
// 5 秒后重连
reconnectTimer = setTimeout(() => {
connectWebSocket(); // 递归重连
}, 5000);
};
// 连接成功时清除重连计时器
socket.onopen = () => {
console.log('重连成功');
clearTimeout(reconnectTimer);
};
return socket;
}
// 初始化连接
const socket = connectWebSocket();
6.2 跨域问题
服务端需配置跨域允许,否则客户端连接会被浏览器拦截。
// ws 库跨域配置(绑定到 HTTP 服务器并设置 CORS)
const http = require('http');
const WebSocket = require('ws');
const server = http.createServer((req, res) => {
// 设置 CORS 头
res.setHeader('Access-Control-Allow-Origin', '*');
res.end();
});
// 绑定到 HTTP 服务器,而非直接监听端口
const wss = new WebSocket.Server({ server });
server.listen(8080, () => {
console.log('支持跨域的 WebSocket 服务启动');
});
6.3 高并发处理
大量客户端连接时,需优化性能(如使用集群、二进制传输)。
// 使用 Node.js 集群(cluster)利用多核 CPU(ws 库)
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const WebSocket = require('ws');
if (cluster.isPrimary) {
// 主进程:创建工作进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
// 工作进程:启动 WebSocket 服务
const wss = new WebSocket.Server({ port: 8080 });
console.log(`工作进程 ${process.pid} 启动`);
}
七、总结
WebSocket 是实时双向通信的核心技术,通过客户端 WebSocket API 和服务端库(ws/Socket.IO)可快速实现实时聊天、数据监控、多人游戏等场景。初学者需掌握:
- 连接建立、事件监听、消息发送的基础用法;
- 服务端广播、单发、房间管理等核心功能;
- 断线重连、跨域、高并发等难点的解决方案。
通过实际案例练习,可快速理解 WebSocket 的工作原理并应用到项目中。



李枭龙10 个月前
AI生成文章:请以上所有知识进行深入分析,确定主要知识点,为每个知识点撰写详细说明并附上具有代表性且带有清晰注释的代码示例,接着根据内容拟定一个准确反映文档核心的标题,最后严格按照 Markdown 格式进行排版,确保文档规范美观,以满足初学者学习使用的需求。
李枭龙1 年前
X Lucas