WebSocket 实战
本节通过三个实战示例展示 WebSocket 的实际应用场景:聊天室、实时通知系统和二进制数据传输。每个示例包含前端实现和核心架构说明。
前置知识
阅读本节前,建议先了解:连接管理与重连
基础概念
WebSocket 实战场景
| 场景 | 数据类型 | 通信模式 | 特点 |
|---|---|---|---|
| 聊天室 | JSON 文本 | 多对多广播 | 频繁短消息 |
| 实时通知 | JSON 文本 | 服务器推送 | 服务器主动推送 |
| 二进制传输 | ArrayBuffer | 双向 | 大数据量传输 |
实战示例一:聊天室前端
消息协议
javascript
// 聊天室消息类型
const MsgType = {
JOIN: 'join', MESSAGE: 'message', LEAVE: 'leave',
WELCOME: 'welcome', USER_JOIN: 'user_join',
USER_LEAVE: 'user_leave', CHAT_MESSAGE: 'chat_message',
USER_LIST: 'user_list'
};完整聊天室
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket 聊天室</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; background: #f5f7fa; }
.chat-box {
max-width: 600px; margin: 0 auto; background: white;
border-radius: 12px; overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.chat-header {
padding: 16px 20px; background: #1a73e8; color: white;
display: flex; justify-content: space-between; align-items: center;
}
.chat-header h2 { font-size: 18px; margin: 0; }
.online { font-size: 13px; opacity: 0.8; }
.messages {
height: 400px; overflow-y: auto; padding: 16px;
display: flex; flex-direction: column; gap: 10px;
}
.msg {
max-width: 70%; padding: 10px 14px; border-radius: 12px;
font-size: 14px; line-height: 1.5;
}
.msg.self {
align-self: flex-end; background: #1a73e8; color: white;
border-bottom-right-radius: 4px;
}
.msg.other {
align-self: flex-start; background: #f0f0f0; color: #333;
border-bottom-left-radius: 4px;
}
.msg .sender { font-size: 12px; font-weight: 600; margin-bottom: 2px; }
.msg .time { font-size: 11px; opacity: 0.6; margin-top: 2px; }
.system { text-align: center; font-size: 12px; color: #999; }
.chat-input {
display: flex; padding: 12px; border-top: 1px solid #eee; gap: 10px;
}
.chat-input input {
flex: 1; padding: 10px 14px; border: 2px solid #e0e0e0;
border-radius: 8px; font-size: 14px; outline: none;
}
.chat-input input:focus { border-color: #1a73e8; }
.send-btn {
padding: 10px 24px; background: #1a73e8; color: white;
border: none; border-radius: 8px; cursor: pointer; font-weight: 600;
}
.login-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); display: flex;
align-items: center; justify-content: center; z-index: 100;
}
.login-card {
background: white; padding: 32px; border-radius: 12px;
text-align: center; min-width: 300px;
}
.login-card input {
width: 100%; padding: 10px; margin: 12px 0;
border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px;
}
.login-card button {
width: 100%; padding: 12px; background: #1a73e8; color: white;
border: none; border-radius: 8px; font-size: 14px; font-weight: 600;
cursor: pointer;
}
</style>
</head>
<body>
<div class="login-overlay" id="loginOverlay">
<div class="login-card">
<h2>加入聊天室</h2>
<input type="text" id="username" placeholder="输入昵称" autofocus>
<button onclick="joinChat()">进入聊天室</button>
</div>
</div>
<div class="chat-box">
<div class="chat-header">
<h2>WebSocket 聊天室</h2>
<span class="online" id="onlineCount">在线: 0</span>
</div>
<div class="messages" id="messages"></div>
<div class="chat-input">
<input type="text" id="chatInput" placeholder="输入消息..."
onkeydown="if(event.key==='Enter')sendMessage()">
<button class="send-btn" onclick="sendMessage()">发送</button>
</div>
</div>
<script>
let ws = null;
let myName = '';
function joinChat() {
myName = document.getElementById('username').value.trim();
if (!myName) return;
document.getElementById('loginOverlay').style.display = 'none';
// 连接 WebSocket(使用 echo 服务器模拟)
ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = () => {
addSystemMsg(`${myName} 加入了聊天室`);
ws.send(JSON.stringify({ type: 'join', name: myName }));
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
handleServerMessage(data);
} catch {
// echo 服务器直接返回消息,模拟聊天
addMessage(event.data, 'echo');
}
};
ws.onclose = () => addSystemMsg('连接已断开');
ws.onerror = () => addSystemMsg('连接错误');
}
function handleServerMessage(data) {
switch (data.type) {
case 'chat_message':
addMessage(data.content, data.sender === myName ? 'self' : data.sender);
break;
case 'user_join':
addSystemMsg(`${data.name} 加入了聊天室`);
break;
case 'user_leave':
addSystemMsg(`${data.name} 离开了聊天室`);
break;
case 'user_list':
document.getElementById('onlineCount').textContent = `在线: ${data.users.length}`;
break;
}
}
function sendMessage() {
const input = document.getElementById('chatInput');
const text = input.value.trim();
if (!text || !ws) return;
// 发送消息
ws.send(JSON.stringify({ type: 'message', content: text }));
addMessage(text, 'self');
input.value = '';
}
function addMessage(text, sender) {
const div = document.createElement('div');
const isSelf = sender === 'self';
div.className = `msg ${isSelf ? 'self' : 'other'}`;
div.innerHTML = `
<div class="sender">${isSelf ? myName : sender}</div>
<div>${text}</div>
<div class="time">${new Date().toLocaleTimeString()}</div>
`;
document.getElementById('messages').appendChild(div);
scrollBottom();
}
function addSystemMsg(text) {
const div = document.createElement('div');
div.className = 'system';
div.textContent = text;
document.getElementById('messages').appendChild(div);
scrollBottom();
}
function scrollBottom() {
const el = document.getElementById('messages');
el.scrollTop = el.scrollHeight;
}
window.addEventListener('beforeunload', () => {
if (ws) ws.close(1001, '页面关闭');
});
</script>
</body>
</html>实战示例二:实时通知系统
javascript
// 实时通知 WebSocket 客户端
class NotificationWS {
constructor(url) {
this.url = url;
this.ws = null;
this.handlers = {};
}
connect(token) {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
// 认证
this.ws.send(JSON.stringify({ type: 'auth', token }));
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this._dispatch(data.type, data);
};
}
on(type, handler) {
if (!this.handlers[type]) this.handlers[type] = [];
this.handlers[type].push(handler);
}
_dispatch(type, data) {
(this.handlers[type] || []).forEach(h => h(data));
// 调用浏览器通知
if (type === 'notification' && Notification.permission === 'granted') {
new Notification(data.title, { body: data.body, icon: data.icon });
}
}
disconnect() {
if (this.ws) this.ws.close();
}
}
// 使用
const notiWS = new NotificationWS('wss://example.com/notifications');
notiWS.on('notification', (data) => {
showNotification(data.title, data.body);
});
notiWS.on('badge', (data) => {
updateBadge(data.count);
});实战示例三:二进制数据传输
javascript
// WebSocket 二进制传输示例
const ws = new WebSocket('wss://example.com/binary');
ws.binaryType = 'arraybuffer';
ws.onopen = () => {
// 发送 ArrayBuffer
const buffer = new ArrayBuffer(1024);
const view = new Float64Array(buffer);
for (let i = 0; i < view.length; i++) {
view[i] = Math.random() * 100;
}
ws.send(buffer);
console.log('已发送 ArrayBuffer,大小:', buffer.byteLength);
};
ws.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
const view = new Float64Array(event.data);
console.log('收到二进制数据:', view.length, '个 Float64');
}
};
// 发送 Blob
const blob = new Blob(['Hello WebSocket'], { type: 'text/plain' });
ws.send(blob);注意事项
- 消息格式统一:使用 JSON 封装,包含
type字段 - 认证机制:连接建立后先发送认证消息
- 心跳保活:长时间无消息时发送心跳帧
- 二进制设置:传输二进制前设置
binaryType - 浏览器通知:结合 Notification API 实现桌面通知
最佳实践
- 设计清晰的消息协议,包含类型、数据和元信息
- 使用心跳检测连接状态,避免假死
- 为不同消息类型建立独立处理函数
- 大文件传输时分片发送,避免单帧过大
- 结合
bufferedAmount实现流量控制
下一节