概述

WebSocket 是 HTML5 提供的全双工通信协议,基于 TCP。与 HTTP 不同,WebSocket 建立连接后,客户端和服务端可以随时互相发送数据,无需客户端先发起请求。


一、浏览器端 WebSocket API

1.1 创建连接

1
2
3
4
5
// 连接到 WebSocket 服务端
const ws = new WebSocket('ws://localhost:8080/chat');

// wss 是加密版本(类似 HTTPS)
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');

// 1. 连接建立成功
ws.onopen = function(event) {
console.log('WebSocket 连接已建立');
ws.send('Hello Server!'); // 连接成功后发送第一条消息
};

// 2. 收到服务端消息
ws.onmessage = function(event) {
console.log('收到消息:', event.data);
// event.data 是服务端发来的数据(字符串或 Blob)
};

// 3. 连接出错
ws.onerror = function(error) {
console.error('WebSocket 错误:', error);
};

// 4. 连接关闭
ws.onclose = function(event) {
console.log('连接已关闭');
console.log('关闭代码:', event.code); // 1000=正常关闭
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('这是一条文本消息');

// 发送 JSON 数据
ws.send(JSON.stringify({
type: 'login',
username: 'tom',
password: '123456'
}));

// 发送二进制数据(ArrayBuffer)
const buffer = new ArrayBuffer(8);
ws.send(buffer);

// 主动关闭连接
ws.close(); // 默认关闭码 1000
ws.close(1000, '用户主动离开'); // 自定义关闭码和原因

1.4 连接状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// readyState 表示当前连接状态
// 0 = CONNECTING 正在连接
// 1 = OPEN 已连接,可以通信
// 2 = CLOSING 正在关闭
// 3 = CLOSED 已关闭

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; // 最多重连5次
this.reconnectCount = 0;
this.reconnectDelay = 3000; // 重连间隔3秒

// 外部可设置的回调
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) => {
// 尝试解析 JSON
let data = event.data;
try {
data = JSON.parse(event.data);
} catch (e) {
// 不是 JSON,保持原始字符串
}
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);
};
}

// 发送消息(自动 JSON 序列化)
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; // 每30秒发一次心跳
this.timeout = 10000; // 10秒没收到回复认为断开
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' }));

// 设定超时:10秒内没收到 pong 就认为断了
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
// 1. 检查协议:浏览器只能用 ws:// 或 wss://
// C++ 原生 socket 不能直接连浏览器 WebSocket
// 需要服务端实现 WebSocket 握手协议

// 2. 检查端口
const ws = new WebSocket('ws://localhost:8080');
// 确保服务端在 8080 端口运行

// 3. 检查跨域(浏览器调试)
// F12 → Console → 查看是否有 CORS 错误

Chrome DevTools 调试

  1. F12 → Network 标签
  2. 筛选 WS(只显示 WebSocket 连接)
  3. 点击连接 → 查看 Messages 标签(实时看到收发的每一条消息)
  4. 查看 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) => {}; // 收到消息,e.data 是数据
ws.onerror = (e) => {}; // 出错
ws.onclose = (e) => {}; // 关闭,e.code + e.reason

// 方法
ws.send('文本'); // 发送文本
ws.send(JSON.stringify(obj)); // 发送 JSON
ws.send(arrayBuffer); // 发送二进制
ws.close(); // 关闭连接
ws.close(1000, '原因'); // 带参数关闭

// 属性
ws.readyState // 0=连接中 1=已连接 2=关闭中 3=已关闭
ws.bufferedAmount // 缓冲区中等待发送的字节数

系列文章:后续将实现 C++ 端 WebSocket 服务端,把浏览器的 ws:// 请求和底层 TCP socket 对接起来。