html5:webSocket 基础使用
'# html5:webSocket 基础使用
一、背景与问题
在传统的HTTP通信中,客户端和服务器之间的交互是请求-响应模式。当需要实时双向通信时,HTTP协议的这种单向性会成为瓶颈。例如,一个即时通讯应用需要实时接收消息,但HTTP协议无法在服务器主动推送消息给客户端。
WebSocket协议的出现解决了这一问题。它通过建立持久的双向通信通道,允许服务器主动向客户端发送数据,同时支持客户端向服务器发送数据。这种机制在以下场景中特别有用:
- 实时聊天系统
- 游戏服务器通信
- 实时数据可视化
- 在线协作工具
但WebSocket并非万能,它存在一些限制:
- 无法通过代理服务器(如Nginx)直接转发
- 需要处理连接保持和断开的复杂逻辑
- 对移动端网络变化的适应性较差
- 安全性需要额外保障(如wss协议)
二、基本原理
WebSocket协议基于TCP协议,其通信过程分为两个阶段:
1. HTTP握手阶段
客户端通过HTTP请求建立WebSocket连接,服务器返回101状态码表示切换协议。关键特征包括:
- 协议头包含
Upgrade: websocket和Connection: upgrade - 使用随机生成的
key进行安全校验 - 服务器返回包含
Sec-WebSocket-Accept的响应头
2. WebSocket通信阶段
建立连接后,双方可以进行以下操作:
- 发送文本数据(UTF-8编码)
- 发送二进制数据
- 关闭连接
通信格式使用帧(frame)机制,每个消息被拆分为多个帧。关键字段包括:
FIN:是否是最后一个帧Rsv1-3:保留位,用于扩展Opcode:数据类型(1-文本,2-二进制,8-关闭等)Mask:掩码标志(客户端发送时需设置)
三、环境准备
前端开发环境
- 浏览器支持:所有现代浏览器(Chrome 55+、Firefox 31+、Edge 12+、Safari 9.1+)
- 开发工具:VS Code、Chrome DevTools
后端开发环境
- Node.js 16+
- WebSocket库:
ws(推荐) 依赖安装:
npm install ws
四、核心实现
1. 基础连接建立(前端)
// index.html
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Demo</title>
</head>
<body>
<input type="text" id="message" placeholder="输入消息">
<button onclick="sendMessage()">发送</button>
<pre id="output"></pre>
<script>
const ws = new WebSocket('ws://localhost:8080');
// 连接建立成功
ws.onopen = function() {
console.log('WebSocket连接已建立');
// 可以发送初始消息
ws.send('Hello Server');
};
// 接收消息
ws.onmessage = function(event) {
const output = document.getElementById('output');
output.textContent += `收到消息: ${event.data}\n`;
};
// 连接关闭
ws.onclose = function() {
console.log('WebSocket连接已关闭');
};
// 发送消息
function sendMessage() {
const input = document.getElementById('message');
const msg = input.value;
if (msg) {
ws.send(msg);
input.value = '';
}
}
</script>
</body>
</html>关键代码解释:
new WebSocket()创建连接,协议可指定为wss(加密)onopen事件在连接建立时触发,此时可以发送初始消息onmessage处理服务器发送的消息,注意event.data是原始数据onclose处理连接关闭事件,建议在此进行资源清理
2. 基础连接建立(后端 - Node.js)
// server.js
const WebSocket = require('ws');
const http = require('http');
// 创建HTTP服务器
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end('WebSocket Server is running');
});
// 创建WebSocket服务器
const wss = new WebSocket.Server({ server });
// 处理连接
wss.on('connection', (ws) => {
console.log('客户端连接成功');
// 接收消息
ws.on('message', (message) => {
console.log(`收到消息: ${message}`);
// 向客户端发送消息
ws.send(`服务器收到: ${message}`);
});
// 关闭连接
ws.on('close', () => {
console.log('客户端断开连接');
});
});
// 启动服务器
server.listen(8080, () => {
console.log('WebSocket服务已启动,访问地址:ws://localhost:8080');
});关键代码解释:
- 使用
ws库创建WebSocket服务器 connection事件处理客户端连接message事件处理客户端发送的消息,注意消息类型是Buffer或String- 使用
send()方法发送消息,支持字符串和二进制数据
3. 异常处理(完整示例)
// 带异常处理的WebSocket客户端
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(`收到消息: ${data}`);
// 模拟异常处理
if (Math.random() > 0.5) {
throw new Error('模拟异常');
}
});
ws.on('error', (err) => {
console.error('发生错误:', err.message);
// 处理重连逻辑
setTimeout(() => {
console.log('尝试重新连接...');
const retryWs = new WebSocket('ws://localhost:8080');
retryWs.on('open', () => {
console.log('重新连接成功');
});
}, 3000);
});关键代码解释:
- 使用
on('error')处理连接错误 - 模拟异常处理逻辑,展示错误处理机制
- 实现简单的重连机制
五、完整案例:实时聊天室
项目结构
chat-app/
├── client/
│ ├── index.html
│ └── chat.js
├── server/
│ ├── server.js
│ └── messages.json
└── package.json1. 前端实现(client/index.html)
<!DOCTYPE html>
<html>
<head>
<title>实时聊天室</title>
</head>
<body>
<div>
<input type="text" id="message" placeholder="输入消息">
<button onclick="sendMessage()">发送</button>
</div>
<pre id="chatLog"></pre>
<script src="chat.js"></script>
</body>
</html>2. 前端逻辑(client/chat.js)
const ws = new WebSocket('ws://localhost:8080');
// 显示消息
function showMessage(message) {
const log = document.getElementById('chatLog');
log.textContent += `服务器: ${message}\n`;
}
// 发送消息
function sendMessage() {
const input = document.getElementById('message');
const msg = input.value.trim();
if (msg) {
ws.send(msg);
input.value = '';
}
}
// 处理消息
ws.onmessage = function(event) {
const msg = event.data;
if (msg.startsWith('用户')) {
const log = document.getElementById('chatLog');
log.textContent += `用户: ${msg}\n`;
} else {
showMessage(msg);
}
};3. 后端实现(server/server.js)
const WebSocket = require('ws');
const http = require('http');
const fs = require('fs');
// 读取用户列表
function loadUsers() {
try {
const data = fs.readFileSync('users.json', 'utf8');
return JSON.parse(data);
} catch (err) {
return [];
}
}
// 保存用户列表
function saveUsers(users) {
fs.writeFileSync('users.json', JSON.stringify(users, null, 2), 'utf8');
}
// 创建HTTP服务器
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end('WebSocket Server is running');
});
// 创建WebSocket服务器
const wss = new WebSocket.Server({ server });
// 处理连接
wss.on('connection', (ws) {
// 加载用户列表
const users = loadUsers();
// 发送欢迎消息
ws.send(JSON.stringify({
type: 'users',
data: users
}));
// 处理消息
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
// 处理用户加入
if (data.type === 'join') {
const user = {
id: Date.now(),
name: data.name
};
users.push(user);
saveUsers(users);
// 广播用户加入
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'userJoined',
data: user
}));
}
});
}
// 处理消息发送
if (data.type === 'message') {
// 广播消息
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'message',
data: {
user: data.user,
message: data.message
}
}));
}
});
}
} catch (err) {
console.error('处理消息时发生错误:', err);
}
});
// 处理关闭
ws.on('close', () => {
console.log('客户端断开连接');
});
});4. 用户管理(users.json)
[
{
"id": 1,
"name": "用户1"
},
{
"id": 2,
"name": "用户2"
}
]六、源码解析
1. WebSocket协议握手
在建立连接时,客户端和服务器会进行如下握手:
GET / HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlhVf8qU1h4JyZd6R6CA==
Sec-WebSocket-Version: 13服务器响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pJ62E6c69K8jF6NNqA6m9XgR8=
Sec-WebSocket-Version: 13关键点:
Sec-WebSocket-Key是客户端生成的随机字符串- 服务器使用SHA-1算法生成
Sec-WebSocket-Accept响应值 - 协议版本必须为13
2. 消息帧格式
WebSocket消息由多个帧组成,每个帧包含:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FIN | RSV1 | RSV2 | RSV3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opcode (1) | Mask (1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload length (7-16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Extended payload length (64 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Mask | Payload data (masked) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+关键字段:
- FIN:是否是最后一个帧
- Opcode:0(文本)、1(二进制)、2(关闭)
- Mask:客户端发送时必须设置为1
3. 安全通信(wss)
使用wss://协议进行加密通信:
// 前端
const ws = new WebSocket('wss://secure.example.com:4433');
// 后端
const https = require('https');
const WebSocket = require('ws');
const serverOptions = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
};
const server = https.createServer(serverOptions);
const wss = new WebSocket.Server({ server });七、进阶使用
1. 消息压缩
使用permessage-deflate扩展减少传输数据量:
const WebSocket = require('ws');
const wss = new WebSocket.Server({
server: http.createServer(),
perMessageDeflate: true
});2. 消息路由
wss.on('connection', (ws, req) => {
const path = req.url;
// 根据路径进行路由
if (path === '/chat') {
// 处理聊天消息
} else if (path === '/notifications') {
// 处理通知消息
}
});3. 消息格式化
// 发送JSON消息
ws.send(JSON.stringify({
type: 'message',
user: '用户1',
text: 'Hello, world!'
}));
// 接收JSON消息
ws.on('message', (data) => {
const msg = JSON.parse(data);
console.log(`收到消息: ${msg.text}`);
});八、性能与工程实践
1. 连接保持优化
使用心跳机制保持连接:
setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'ping' })); } }, 30000);使用
keepalive选项:const wss = new WebSocket.Server({ server: http.createServer(), keepalive: true });
2. 消息处理优化
使用
Buffer处理二进制数据:ws.on('message', (data) => { if (data instanceof Buffer) { // 处理二进制数据 } });使用
setImmediate避免阻塞:ws.on('message', (data) => { setImmediate(() => { // 处理消息 }); });
3. 安全实践
使用wss协议:
const ws = new WebSocket('wss://secure.example.com');验证用户身份:
wss.on('connection', (ws, req) => { const authHeader = req.headers['authorization']; if (!authHeader || !isValidToken(authHeader)) { ws.close(401, 'Unauthorized'); return; } });
九、常见问题与踩坑
1. 连接失败常见原因
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 无法连接 | 服务器未运行 | 确认服务器运行 |
| 101错误 | 握手失败 | 检查协议头 |
| 400错误 | 消息格式错误 | 检查消息格式 |
| 1006错误 | 通信异常 | 检查网络连接 |
2. 常见错误示例
错误示例:未处理关闭事件
ws.on('message', (data) => {
// 处理消息
});改进方案:
ws.on('message', (data) => {
// 处理消息
});
ws.on('close', () => {
console.log('连接已关闭');
});3. 消息丢失问题
原因: 未处理onmessage事件或消息队列溢出
解决方案:
ws.on('message', (data) => {
// 使用队列处理消息
messageQueue.push(data);
processMessages();
});4. 安全漏洞
问题: 未验证消息来源
解决方案:
ws.on('message', (data) => {
if (data instanceof Buffer) {
// 验证消息来源
if (!isValidSource(data)) {
ws.close(403, 'Invalid source');
}
}
});十、最佳实践
1. 推荐的使用模式
- 连接管理:使用
readyState检测连接状态 - 消息格式:使用JSON格式化消息,便于解析
- 错误处理:为所有事件添加错误处理逻辑
- 资源释放:在
onclose中释放相关资源 - 性能优化:使用压缩和二进制数据传输
2. 推荐的代码结构
// 前端
const ws = new WebSocket('ws://localhost:8080');
// 连接管理
ws.onopen = () => {
console.log('连接已建立');
};
ws.onmessage = (event) => {
console.log('收到消息:', event.data);
};
ws.onclose = () => {
console.log('连接已关闭');
};
// 消息发送
function sendMessage(message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
}3. 推荐的后端结构
// 后端
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
// 连接处理逻辑
});
// 消息处理
wss.on('message', (message) => {
// 消息处理逻辑
});十一、总结
WebSocket协议为实时双向通信提供了可靠的基础,其核心优势在于:
- 建立持久连接,减少通信延迟
- 支持双向数据传输
- 降低通信开销
在实际开发中,需要根据具体场景选择合适的通信方式:
- 使用WebSocket:实时聊天、游戏、实时数据更新
- 避免使用WebSocket:单次请求、非实时场景、需要代理转发的场景
开发过程中需要注意:
- 正确处理连接生命周期
- 实现完善的错误处理机制
- 使用安全协议(wss)
- 优化消息传输效率
- 处理网络变化和连接保持
通过合理使用WebSocket,可以显著提升应用的实时性和交互性,但同时也需要关注其带来的复杂性和潜在风险。在实际项目中,建议结合具体业务需求和系统架构,选择最适合的通信方案。
评论已关闭