WebSocket
WebSocket简介
WebSocket是一种在单个TCP连接上进行全双工通信的协议,提供了实时通信能力。
WebSocket特点
- 全双工通信:客户端和服务器可以同时发送数据
- 实时性高:无需轮询,减少延迟
- 连接保持:一次连接,长期有效
- 低开销:头部开销小,节省带宽
- 跨域支持:支持跨域通信
- 基于TCP:使用TCP协议,可靠传输
WebSocket与HTTP的区别
| 特性 | HTTP | WebSocket |
|---|---|---|
| 连接方式 | 短连接,请求-响应 | 长连接,全双工 |
| 通信方向 | 单向(客户端→服务器) | 双向(客户端↔服务器) |
| 头部开销 | 大 | 小 |
| 实时性 | 低(需要轮询) | 高(实时通信) |
| 服务器推送 | 无 | 有 |
| 协议标识 | http://, https:// | ws://, wss:// |
WebSocket基本用法
连接建立
javascript
const socket = new WebSocket('ws://localhost:8080');
// 连接打开
socket.onopen = function() {
console.log('WebSocket连接已打开');
socket.send('Hello Server');
};
// 接收消息
socket.onmessage = function(event) {
console.log('收到消息:', event.data);
};
// 连接关闭
socket.onclose = function() {
console.log('WebSocket连接已关闭');
};
// 连接错误
socket.onerror = function(error) {
console.error('WebSocket错误:', error);
};
// 关闭连接
socket.close();发送消息
javascript
// 发送文本消息
socket.send('Hello Server');
// 发送JSON数据
socket.send(JSON.stringify({ type: 'message', content: 'Hello' }));
// 发送二进制数据
const blob = new Blob(['Hello'], { type: 'text/plain' });
socket.send(blob);
// 发送ArrayBuffer
const buffer = new ArrayBuffer(4);
const view = new Uint8Array(buffer);
view[0] = 0x48; // 'H'
view[1] = 0x65; // 'e'
view[2] = 0x6C; // 'l'
view[3] = 0x6C; // 'l'
socket.send(buffer);接收消息
javascript
socket.onmessage = function(event) {
// 文本消息
if (typeof event.data === 'string') {
console.log('文本消息:', event.data);
try {
const data = JSON.parse(event.data);
console.log('JSON消息:', data);
} catch (e) {
console.log('普通文本:', event.data);
}
}
// 二进制消息
else if (event.data instanceof Blob) {
const reader = new FileReader();
reader.onload = function() {
console.log('Blob消息:', reader.result);
};
reader.readAsText(event.data);
}
// ArrayBuffer消息
else if (event.data instanceof ArrayBuffer) {
const view = new Uint8Array(event.data);
console.log('ArrayBuffer消息:', view);
}
};WebSocket心跳检测
心跳检测原理
心跳检测是通过定期发送心跳包来检测连接是否正常的机制,防止连接被网络设备(如防火墙)断开。
实现心跳检测
javascript
class WebSocketWithHeartbeat {
constructor(url, heartbeatInterval = 30000) {
this.url = url;
this.heartbeatInterval = heartbeatInterval;
this.heartbeatTimer = null;
this.reconnectTimer = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.socket = null;
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('WebSocket连接已打开');
this.reconnectAttempts = 0;
this.startHeartbeat();
};
this.socket.onmessage = (event) => {
if (event.data === 'pong') {
console.log('收到心跳响应');
} else {
console.log('收到消息:', event.data);
}
};
this.socket.onclose = () => {
console.log('WebSocket连接已关闭');
this.stopHeartbeat();
this.reconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket错误:', error);
};
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send('ping');
console.log('发送心跳');
}
}, this.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
reconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.reconnectTimer = setTimeout(() => {
this.connect();
}, 3000);
} else {
console.error('重连失败');
}
}
send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(data);
} else {
console.error('WebSocket未连接');
}
}
close() {
this.stopHeartbeat();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
if (this.socket) {
this.socket.close();
}
}
}
const socket = new WebSocketWithHeartbeat('ws://localhost:8080');
socket.send('Hello Server');WebSocket断线重连
断线重连策略
- 指数退避:重连间隔逐渐增加
- 最大重连次数:防止无限重连
- 网络状态检测:在网络恢复时重连
实现断线重连
javascript
class ReconnectingWebSocket {
constructor(url, options = {}) {
this.url = url;
this.options = {
maxReconnectAttempts: 5,
reconnectInterval: 3000,
maxReconnectInterval: 30000,
...options
};
this.socket = null;
this.reconnectAttempts = 0;
this.reconnectTimer = null;
this.isClosing = false;
this.eventListeners = {
open: [],
message: [],
close: [],
error: []
};
this.connect();
}
connect() {
try {
this.socket = new WebSocket(this.url);
this.socket.onopen = (event) => {
console.log('WebSocket连接已打开');
this.reconnectAttempts = 0;
this.eventListeners.open.forEach(listener => listener(event));
};
this.socket.onmessage = (event) => {
this.eventListeners.message.forEach(listener => listener(event));
};
this.socket.onclose = (event) => {
console.log('WebSocket连接已关闭');
this.eventListeners.close.forEach(listener => listener(event));
if (!this.isClosing) {
this.scheduleReconnect();
}
};
this.socket.onerror = (error) => {
console.error('WebSocket错误:', error);
this.eventListeners.error.forEach(listener => listener(error));
};
} catch (error) {
console.error('WebSocket连接失败:', error);
this.scheduleReconnect();
}
}
scheduleReconnect() {
if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.min(
this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1),
this.options.maxReconnectInterval
);
console.log(`尝试重连 (${this.reconnectAttempts}/${this.options.maxReconnectAttempts}),延迟 ${delay}ms`);
this.reconnectTimer = setTimeout(() => {
this.connect();
}, delay);
} else {
console.error('重连失败,已达到最大重连次数');
}
}
send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(data);
return true;
} else {
console.error('WebSocket未连接,无法发送消息');
return false;
}
}
close() {
this.isClosing = true;
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
if (this.socket) {
this.socket.close();
}
}
on(event, listener) {
if (this.eventListeners[event]) {
this.eventListeners[event].push(listener);
}
}
off(event, listener) {
if (this.eventListeners[event]) {
this.eventListeners[event] = this.eventListeners[event].filter(l => l !== listener);
}
}
get readyState() {
return this.socket ? this.socket.readyState : WebSocket.CLOSED;
}
}
const socket = new ReconnectingWebSocket('ws://localhost:8080');
socket.on('open', () => {
console.log('连接已打开,发送消息');
socket.send('Hello Server');
});
socket.on('message', (event) => {
console.log('收到消息:', event.data);
});
socket.on('close', () => {
console.log('连接已关闭');
});
socket.on('error', (error) => {
console.error('连接错误:', error);
});
// 关闭连接
// socket.close();WebSocket服务器实现
Node.js实现
javascript
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('客户端已连接');
// 发送欢迎消息
ws.send('Welcome to WebSocket Server');
// 接收消息
ws.on('message', (message) => {
console.log('收到消息:', message);
// 处理心跳
if (message === 'ping') {
ws.send('pong');
return;
}
// 广播消息
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(`Server: ${message}`);
}
});
});
// 连接关闭
ws.on('close', () => {
console.log('客户端已断开连接');
});
// 连接错误
ws.on('error', (error) => {
console.error('连接错误:', error);
});
});
console.log('WebSocket服务器已启动,监听端口8080');使用Socket.io
javascript
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server);
io.on('connection', (socket) => {
console.log('客户端已连接');
// 发送欢迎消息
socket.emit('message', 'Welcome to Socket.io Server');
// 接收消息
socket.on('message', (message) => {
console.log('收到消息:', message);
// 广播消息
io.emit('message', `Server: ${message}`);
});
// 房间操作
socket.on('joinRoom', (room) => {
socket.join(room);
console.log(`客户端加入房间: ${room}`);
io.to(room).emit('message', `有人加入了房间 ${room}`);
});
// 连接关闭
socket.on('disconnect', () => {
console.log('客户端已断开连接');
});
});
server.listen(8080, () => {
console.log('Socket.io服务器已启动,监听端口8080');
});WebSocket安全
使用WSS
javascript
// 使用wss://协议
const socket = new WebSocket('wss://example.com');认证
javascript
// 使用token认证
const token = 'your-auth-token';
const socket = new WebSocket(`ws://localhost:8080?token=${token}`);消息验证
javascript
// 消息格式验证
function validateMessage(message) {
try {
const data = JSON.parse(message);
return data.type && typeof data.type === 'string';
} catch (e) {
return false;
}
}
socket.onmessage = function(event) {
if (validateMessage(event.data)) {
const data = JSON.parse(event.data);
// 处理消息
} else {
console.error('无效的消息格式');
}
};WebSocket应用场景
- 实时聊天:即时通讯应用
- 在线游戏:实时多人游戏
- 实时协作:在线文档编辑
- 实时数据:股票行情、体育赛事
- 推送通知:系统通知、消息提醒
- 视频通话:WebRTC信令
面试常见问题
1. WebSocket的特点?
- 全双工通信
- 实时性高
- 连接保持
- 低开销
- 跨域支持
2. WebSocket与HTTP的区别?
- HTTP:短连接,请求-响应,单向通信,头部开销大
- WebSocket:长连接,全双工,双向通信,头部开销小
3. WebSocket的协议标识?
- ws://:未加密
- wss://:加密(基于TLS)
4. 如何实现WebSocket的心跳检测?
通过定期发送心跳包(如ping),服务器响应(如pong)来检测连接是否正常。
5. 如何实现WebSocket的断线重连?
在onclose事件中触发重连逻辑,使用指数退避策略,设置最大重连次数。
6. WebSocket支持哪些数据类型?
- 字符串
- Blob
- ArrayBuffer
7. WebSocket的readyState有哪些状态?
- 0: CONNECTING - 连接中
- 1: OPEN - 连接已打开
- 2: CLOSING - 关闭中
- 3: CLOSED - 连接已关闭
8. 如何处理WebSocket的错误?
通过onerror事件监听器捕获错误,实现错误处理逻辑。
9. WebSocket的优势是什么?
- 实时性高
- 减少网络开销
- 简化代码逻辑
- 支持服务器推送
10. WebSocket的局限性是什么?
- 服务器资源消耗较大
- 可能被防火墙阻止
- 不支持所有浏览器(旧版本)
通过理解WebSocket的各种特性和应用场景,可以更好地构建实时通信应用。