1. 概念与原理
什么是 WebSocket?
WebSocket 是一种在单个 TCP 连接上进行全双工通信的网络协议。它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
关键特性:
全双工通信:客户端和服务器可以同时发送和接收数据,就像打电话一样。
建立在 TCP 之上:WebSocket 本身是一个应用层协议,依赖于底层的 TCP 连接。
低开销:在建立连接后,数据传输的头信息很小,通常只有 2-10 字节,远小于 HTTP 请求的头部。
实时性:服务器可以主动、即时地向客户端推送消息,无需客户端轮询。
与 HTTP 的关系与区别
联系:WebSocket 连接通常通过一个 HTTP 升级请求 来建立。客户端发送一个特殊的 HTTP 请求,告诉服务器希望将协议升级为 WebSocket。
区别:
| 特性 | HTTP | WebSocket |
|---|---|---|
| 通信模式 | 半双工(请求-响应) | 全双工 |
| 连接生命周期 | 短连接(通常) | 长连接 |
| 数据推送 | 只能由客户端发起(轮询) | 服务端可主动推送 |
| 头部开销 | 每次请求/响应都包含完整头部 | 连接建立后,头部极小 |
工作原理:握手与数据传输
握手阶段
客户端发送一个 HTTP 请求,头信息中包含:
text
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 随机生成的密钥 Sec-WebSocket-Version: 13
服务器返回一个 HTTP 101 状态码(切换协议)的响应:
text
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 基于客户端Key计算得出的
至此,握手完成,底层的 TCP 连接保持不变,但通信协议从 HTTP 切换到了 WebSocket。
数据传输阶段
连接建立后,客户端和服务器就可以通过这个连接互相发送消息。
数据以“帧”(Frame)的形式传输。WebSocket 协议定义了如何将消息拆分成帧,以及如何组装帧成消息。
消息可以是文本数据,也可以是二进制数据(如文件、图片)。
2. 实际项目中使用
在实际项目中,我们通常不会从零实现 WebSocket 协议,而是使用成熟的库。
前端(浏览器端)
浏览器提供了原生的 API。
WebSocket
// 1. 创建 WebSocket 连接
const socket = new WebSocket('ws://localhost:8080/chat');
// 2. 监听连接打开事件
socket.onopen = function(event) {
console.log('连接已建立');
// 连接建立后,发送一条消息
socket.send('你好,服务器!');
};
// 3. 监听接收消息事件
socket.onmessage = function(event) {
console.log('收到服务器消息:', event.data);
// 通常数据是 JSON 字符串,需要解析
// const data = JSON.parse(event.data);
// updateUI(data);
};
// 4. 监听连接关闭事件
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`连接已正常关闭,代码=${event.code},原因=${event.reason}`);
} else {
console.error('连接意外断开');
}
};
// 5. 监听错误事件
socket.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
// 发送消息(可以在按钮点击等事件中调用)
function sendMessage(message) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: 'chat', content: message }));
}
}
// 关闭连接
function closeConnection() {
socket.close(1000, '用户主动关闭');
}
后端(Node.js 示例,使用 ws 库)
最常用的是 库,轻量且高效。
ws
npm install ws
const WebSocket = require('ws');
// 创建 WebSocket 服务器,监听 8080 端口
const wss = new WebSocket.Server({ port: 8080 });
// 监听连接事件
wss.on('connection', function connection(ws, request) {
// ws 代表与客户端的连接
const clientIp = request.socket.remoteAddress;
console.log(`新的客户端连接: ${clientIp}`);
// 监听来自当前客户端的消息
ws.on('message', function incoming(message) {
console.log('收到客户端消息: %s', message);
try {
const data = JSON.parse(message);
// 根据消息类型进行处理
switch (data.type) {
case 'chat':
// 广播消息给所有连接的客户端
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'chat',
user: clientIp,
content: data.content,
timestamp: Date.now()
}));
}
});
break;
default:
console.warn('未知的消息类型:', data.type);
}
} catch (e) {
console.error('解析消息失败:', e);
}
});
// 向当前客户端发送欢迎消息
ws.send(JSON.stringify({
type: 'system',
content: '欢迎加入聊天室!'
}));
// 监听连接关闭
ws.on('close', function close() {
console.log(`客户端 ${clientIp} 已断开连接`);
});
});
console.log('WebSocket 服务器运行在 ws://localhost:8080');
其他后端语言
Java: 使用 或 Spring Framework 的 WebSocket 模块。
Java-WebSocket
Python: 使用 (异步) 或
websockets。
Flask-SocketIO
Go: 使用 。
gorilla/websocket
3. 使用过程中需要注意什么?
连接状态管理
始终检查 属性(
readyState,
CONNECTING,
OPEN,
CLOSING)再发送消息。
CLOSED
实现自动重连机制,因为网络不稳定或服务器重启会导致连接中断。
心跳保活
长时间不通信,中间的网络节点(如代理、负载均衡器)可能会断开空闲的 TCP 连接。
需要定期(如每 30 秒)从客户端或服务器发送一个小的“心跳”数据包(Ping/Pong),以保持连接活跃。 库有内置支持。
ws
安全性
使用 (WebSocket Secure),它类似于 HTTPS,在 TLS 之上加密数据。
wss://
在服务端对客户端发来的消息进行严格的验证和过滤,防止注入攻击。
在握手阶段进行身份认证(例如,在 URL 中传递 Token,或在 Cookie 中验证 Session)。
可扩展性
单机 WebSocket 服务器有连接数上限。当需要支持大量并发连接时,需要使用多台服务器。
在多服务器环境下,需要引入 Pub/Sub 系统(如 Redis)来在不同服务器的 WebSocket 连接之间广播消息。
4. 需要避免踩哪些坑?
忽视连接断开和重连
坑:没有实现自动重连,导致连接一旦断开,应用就“死”了。
避坑:在 事件中,使用指数退避算法实现自动重连。
onclose
let reconnectInterval = 1000; // 初始重连间隔
function connect() {
const socket = new WebSocket('ws://...');
// ... 设置监听器 ...
socket.onclose = function(e) {
console.log('连接断开,尝试重连...');
setTimeout(() => {
reconnectInterval *= 1.5; // 每次重连间隔增加
connect();
}, reconnectInterval);
};
}
connect();
在连接建立前就发送消息
坑:在 后立即调用
new WebSocket(),此时连接尚未建立 (
send() 为
readyState),消息会丢失。
CONNECTING
避坑:在 事件回调中或之后发送消息。
onopen
未处理消息边界和粘包
坑:认为一次 事件就对应一次
onmessage 发送的完整消息。虽然 WebSocket 协议本身保证了消息的完整性,但在设计应用层协议时,如果传输的是流式数据,需要自己定义消息边界。
send
避坑:对于复杂场景,可以在应用层定义简单的协议,例如在消息头部加上长度前缀。
内存泄漏
坑:在服务端,对于已断开连接的客户端,没有及时清理其事件监听器和引用,导致垃圾回收无法进行。
避坑:在 或
onclose 事件中,移除不必要的监听器。
onerror
javascript
// 在 ws 库中,通常不需要手动移除,因为连接对象会被自动回收。 // 但如果添加了自定义监听器到外部对象,则需要手动清理。
负载均衡器配置不当
坑:在服务器前使用了 HTTP 负载均衡器,但没有配置对 WebSocket 的支持。
避坑:
确保负载均衡器支持并启用了对 WebSocket 的代理(例如,Nginx 需要设置 和
Upgrade 头)。
Connection
使用支持 WebSocket 的负载均衡策略(如 IP Hash),因为 WebSocket 是长连接,需要保持会话亲和性(Session Affinity)。
Nginx 配置示例:
location /chat/ {
proxy_pass http://backend_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
总结
WebSocket 是实现现代 Web 应用实时功能的利器。掌握其核心概念(全双工、握手),学会使用成熟的客户端和服务端库,并特别注意连接管理、心跳、安全性和可扩展性,你就能有效地利用它来构建聊天应用、实时游戏、在线协作工具等丰富多彩的实时功能,同时避开常见的陷阱。






![[C++探索之旅] 第一部分第十一课:小练习,猜单词 - 鹿快](https://img.lukuai.com/blogimg/20251015/da217e2245754101b3d2ef80869e9de2.jpg)










暂无评论内容