概述
WebSocket 是 HTML5 提供的全双工通信协议,基于 TCP。与 HTTP 不同,WebSocket 建立连接后,客户端和服务端可以随时互相发送数据,无需客户端先发起请求。
一、浏览器端 WebSocket API
1.1 创建连接
1 2 3 4 5
| const ws = new WebSocket('ws://localhost:8080/chat');
const wss = new WebSocket('wss://example.com/chat');
|
1.2 四种核心事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const ws = new WebSocket('ws://localhost:8080');
ws.onopen = function(event) { console.log('WebSocket 连接已建立'); ws.send('Hello Server!'); };
ws.onmessage = function(event) { console.log('收到消息:', event.data); };
ws.onerror = function(error) { console.error('WebSocket 错误:', error); };
ws.onclose = function(event) { console.log('连接已关闭'); console.log('关闭代码:', event.code); console.log('关闭原因:', event.reason); };
|
1.3 常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const ws = new WebSocket('ws://localhost:8080');
ws.send('这是一条文本消息');
ws.send(JSON.stringify({ type: 'login', username: 'tom', password: '123456' }));
const buffer = new ArrayBuffer(8); ws.send(buffer);
ws.close(); ws.close(1000, '用户主动离开');
|
1.4 连接状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
console.log(ws.readyState);
if (ws.readyState === WebSocket.OPEN) { ws.send('立即发送'); } else { ws.onopen = () => ws.send('连接后发送'); }
|
二、完整前端封装示例
2.1 带自动重连的 WebSocket 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| class ChatWebSocket { constructor(url) { this.url = url; this.ws = null; this.reconnectTimer = null; this.maxReconnect = 5; this.reconnectCount = 0; this.reconnectDelay = 3000; this.onMessage = null; this.onOpen = null; this.onClose = null; this.onError = null; this.connect(); }
connect() { this.ws = new WebSocket(this.url);
this.ws.onopen = () => { console.log('✅ WebSocket 连接成功'); this.reconnectCount = 0; if (this.onOpen) this.onOpen(); };
this.ws.onmessage = (event) => { let data = event.data; try { data = JSON.parse(event.data); } catch (e) { } if (this.onMessage) this.onMessage(data); };
this.ws.onclose = (event) => { console.log(`连接关闭 (code: ${event.code})`); if (this.onClose) this.onClose(event); this.tryReconnect(); };
this.ws.onerror = (error) => { console.error('WebSocket 错误:', error); if (this.onError) this.onError(error); }; }
send(data) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { const payload = typeof data === 'string' ? data : JSON.stringify(data); this.ws.send(payload); return true; } console.warn('WebSocket 未连接,无法发送'); return false; }
tryReconnect() { if (this.reconnectCount >= this.maxReconnect) { console.error('已达最大重连次数,停止重连'); return; } this.reconnectCount++; console.log(`将在 ${this.reconnectDelay}ms 后第 ${this.reconnectCount} 次重连...`); this.reconnectTimer = setTimeout(() => this.connect(), this.reconnectDelay); }
close() { clearTimeout(this.reconnectTimer); if (this.ws) { this.ws.close(1000, '用户主动关闭'); } } }
const chat = new ChatWebSocket('ws://localhost:8080/chat');
chat.onMessage = (data) => { console.log('收到:', data); if (data.type === 'chat') { displayMessage(data.text, data.from); } };
chat.onOpen = () => { chat.send({ type: 'login', username: 'tom' }); };
chat.send({ type: 'chat', text: '大家好!', to: 'all' });
|
三、消息类型设计(协议层)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const MessageType = { LOGIN: 'login', LOGIN_OK: 'login_ok', LOGIN_FAIL: 'login_fail', TEXT_MSG: 'text_msg', IMAGE_MSG: 'image_msg', FRIEND_REQ: 'friend_req', FRIEND_ACK: 'friend_ack', HEARTBEAT: 'heartbeat', ERROR: 'error', };
function buildMessage(type, data) { return { type: type, timestamp: Date.now(), data: data }; }
ws.send(JSON.stringify(buildMessage(MessageType.LOGIN, { username: 'tom', password: 'xxx' })));
ws.send(JSON.stringify(buildMessage(MessageType.TEXT_MSG, { from: 'tom', to: 'jerry', content: '你好啊!' })));
|
四、心跳机制(保持连接)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class HeartbeatWS { constructor(url) { this.url = url; this.ws = null; this.heartbeatTimer = null; this.heartbeatInterval = 30000; this.timeout = 10000; this.timeoutTimer = null; this.connect(); }
connect() { this.ws = new WebSocket(this.url);
this.ws.onopen = () => { console.log('连接成功'); this.startHeartbeat(); };
this.ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'pong') { this.resetTimeout(); console.log('心跳正常'); return; } };
this.ws.onclose = () => { this.stopHeartbeat(); }; }
startHeartbeat() { this.heartbeatTimer = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'ping' })); this.timeoutTimer = setTimeout(() => { console.warn('心跳超时,主动断开'); this.ws.close(); }, this.timeout); } }, this.heartbeatInterval); }
resetTimeout() { clearTimeout(this.timeoutTimer); }
stopHeartbeat() { clearInterval(this.heartbeatTimer); clearTimeout(this.timeoutTimer); } }
|
五、WebSocket vs HTTP
|
HTTP |
WebSocket |
| 通信模式 |
请求-响应(客户端主动) |
全双工(双方随时发) |
| 连接方式 |
短连接(一次请求一次响应) |
长连接(一直连着) |
| 头部开销 |
每次请求都有 HTTP 头(~800字节) |
建立后帧头仅 2-14 字节 |
| 适用场景 |
网页加载、API 调用、表单提交 |
聊天、实时推送、游戏、协同编辑 |
| 协议标识 |
http:// / https:// |
ws:// / wss:// |
六、常见问题与调试
连接不上?
1 2 3 4 5 6 7 8 9 10
|
const ws = new WebSocket('ws://localhost:8080');
|
- F12 → Network 标签
- 筛选 WS(只显示 WebSocket 连接)
- 点击连接 → 查看 Messages 标签(实时看到收发的每一条消息)
- 查看 Frames 标签(二进制帧数据)
服务端如何支持 WebSocket?
WebSocket 需要服务端实现特殊的握手协议。C++ 原生 socket 收到的是 HTTP 升级请求:
1 2 3 4 5
| GET /chat HTTP/1.1 Host: localhost:8080 Upgrade: websocket ← 关键头 Connection: Upgrade ← 关键头 Sec-WebSocket-Key: xxx ← 用于验证
|
服务端必须按 RFC 6455 规范回复并完成握手,后续才能收发 WebSocket 帧。这部分将在后续的 C++ WebSocket 服务端实现中详细讲解。
七、速查表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const ws = new WebSocket('ws://地址');
ws.onopen = () => {}; ws.onmessage = (e) => {}; ws.onerror = (e) => {}; ws.onclose = (e) => {};
ws.send('文本'); ws.send(JSON.stringify(obj)); ws.send(arrayBuffer); ws.close(); ws.close(1000, '原因');
ws.readyState ws.bufferedAmount
|
系列文章:后续将实现 C++ 端 WebSocket 服务端,把浏览器的 ws:// 请求和底层 TCP socket 对接起来。