WebSocket 基础篇
# WebSocket 前端基础篇
# 一、为什么需要 WebSocket?
初次接触 WebSocket 的人,通常会问:我们已经有了 HTTP 协议,为什么还需要另一个协议?WebSocket 能带来什么好处?
答案很简单:因为 HTTP 协议的通信模式有局限性:通信只能由客户端发起。
举个例子,假设我们想要获取实时天气信息,通常的 HTTP 请求流程是由客户端(如浏览器)向服务器发起请求,服务器返回结果。HTTP 协议无法让服务器主动向客户端推送更新的信息。
由于 HTTP 协议的单向请求特性,如果服务器的状态经常变化而客户端需要实时获取最新状态,这种情况就很麻烦。为了解决这个问题,通常采用轮询(Polling)的方式:客户端每隔一段时间就向服务器发送请求,询问是否有新信息。典型场景如聊天室。
然而,轮询效率低且资源浪费,因为它需要客户端不断地发起请求,或保持连接长时间打开。为了提高效率并解决这些问题,工程师们设计了 WebSocket。
# 二、WebSocket 简介
WebSocket 协议诞生于 2008 年,并在 2011 年成为国际标准。如今几乎所有主流浏览器都已支持。
WebSocket 最大的特点是:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现真正的双向通信。 这使 WebSocket 成为一种重要的服务器推送技术。
WebSocket 其他特点包括:
- 建立在 TCP 协议之上,服务器端实现相对简单。
- 与 HTTP 协议兼容性良好,使用相同的端口(80 和 443),握手阶段采用 HTTP 协议,使其容易通过防火墙和代理。
- 轻量的数据格式,减少传输开销,提高通信效率。
- 支持文本和二进制数据传输,灵活应对不同场景。
- 没有同源限制,允许客户端与任意服务器通信。
- 协议标识符为
ws
(加密为wss
),服务器网址类似于 URL。
ws://example.com:80/some/path
# 三、WebSocket 客户端的简单示例
WebSocket 的基本用法非常简单。以下是一个基础的 WebSocket 客户端脚本示例:
// 创建 WebSocket 连接
var ws = new WebSocket("wss://echo.websocket.org");
// 当连接成功时触发
ws.onopen = function(evt) {
console.log("WebSocket 连接已打开...");
// 连接成功后,向服务器发送消息
ws.send("Hello WebSockets!");
};
// 当接收到服务器消息时触发
ws.onmessage = function(evt) {
console.log("收到服务器消息: " + evt.data);
// 在收到消息后关闭 WebSocket 连接
ws.close();
};
// 当连接关闭时触发
ws.onclose = function(evt) {
console.log("WebSocket 连接已关闭。");
};
// 当连接发生错误时触发
ws.onerror = function(evt) {
console.error("WebSocket 连接发生错误: " + evt);
};
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
详细注释:
var ws = new WebSocket("wss://echo.websocket.org");
- 创建一个 WebSocket 对象并建立连接。
wss://
表示使用加密的 WebSocket 协议(类似 HTTPS)。 - 连接地址为
wss://echo.websocket.org
,这是一个用于测试的 WebSocket 服务器,它会将收到的消息原样返回。
- 创建一个 WebSocket 对象并建立连接。
ws.onopen = function(evt) { ... }
- 当 WebSocket 连接成功时,触发
onopen
事件。在这个事件中,可以执行连接后的初始化操作,如发送第一条消息。
- 当 WebSocket 连接成功时,触发
ws.send("Hello WebSockets!");
- 使用
send
方法发送数据。这里发送了一条简单的文本消息。
- 使用
ws.onmessage = function(evt) { ... }
- 当服务器发送消息时,触发
onmessage
事件。接收到的消息会包含在evt.data
中。在本例中,收到消息后会打印到控制台,并立即关闭连接。
- 当服务器发送消息时,触发
ws.onclose = function(evt) { ... }
- 当连接关闭时,触发
onclose
事件。通常用于清理资源或通知用户连接已结束。
- 当连接关闭时,触发
ws.onerror = function(evt) { ... }
- 当连接出错时,触发
onerror
事件。这种情况下,可能需要处理错误或重试连接。
- 当连接出错时,触发
WebSocket 的常见应用场景
- 实时聊天应用:如微信、Slack 等。
- 实时通知系统:如股票价格推送、服务器监控报警等。
- 在线多人游戏:如多人协作游戏、对战游戏等。
- 实时数据分析与监控:如流媒体直播、运动数据分析等。
# 四、WebSocket 客户端 API 详解
在使用 WebSocket 时,了解客户端 API 的使用方法非常重要。以下是对常见 WebSocket 客户端 API 的详细总结,包括每个 API 的作用、使用方式,以及相关代码的详细注释。
# 4.1 WebSocket 构造函数
用法:通过 WebSocket 构造函数来新建一个 WebSocket 实例,建立客户端与服务器之间的连接。
// 创建一个 WebSocket 实例,指定要连接的服务器地址
var ws = new WebSocket('ws://localhost:8080');
// 当上面代码执行后,客户端将会与指定服务器进行连接
2
3
4
说明:WebSocket 构造函数接受一个 URL 参数,该参数指定要连接的服务器地址。ws://
表示非加密连接,wss://
表示加密连接。
# 4.2 WebSocket.readyState
用法:readyState
属性表示当前 WebSocket 连接的状态。
// 根据 WebSocket 的状态采取相应操作
switch (ws.readyState) {
case WebSocket.CONNECTING:
// 正在连接中
console.log('WebSocket 正在连接...');
break;
case WebSocket.OPEN:
// 连接已建立,可以通信
console.log('WebSocket 连接已打开');
break;
case WebSocket.CLOSING:
// 连接正在关闭
console.log('WebSocket 正在关闭...');
break;
case WebSocket.CLOSED:
// 连接已关闭或连接失败
console.log('WebSocket 连接已关闭');
break;
default:
// 不会发生,保留处理
console.log('未知的 WebSocket 状态');
break;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
状态值说明:
CONNECTING (0)
:正在建立连接。OPEN (1)
:连接已建立,可以通信。CLOSING (2)
:连接正在关闭。CLOSED (3)
:连接已关闭或连接失败。
# 4.3 WebSocket.onopen
用法:onopen
事件用于在连接成功后触发回调函数。
// 监听 WebSocket 连接成功事件
ws.onopen = function () {
console.log('WebSocket 连接成功');
// 连接成功后向服务器发送消息
ws.send('Hello Server!');
};
// 使用 addEventListener 添加多个回调函数
ws.addEventListener('open', function (event) {
console.log('另一个连接成功的回调');
ws.send('Another Hello Server!');
});
2
3
4
5
6
7
8
9
10
11
12
说明:onopen
是一个事件处理函数,当 WebSocket 连接成功时触发。可以使用 addEventListener
添加多个回调函数。
# 4.4 WebSocket.onclose
用法:onclose
事件用于在连接关闭后触发回调函数。
// 监听 WebSocket 连接关闭事件
ws.onclose = function (event) {
// 获取关闭的状态码、原因和是否是干净关闭
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
console.log(`WebSocket 连接关闭,状态码: ${code}, 原因: ${reason}, 是否干净关闭: ${wasClean}`);
};
// 使用 addEventListener 监听关闭事件
ws.addEventListener('close', function (event) {
console.log('WebSocket 已关闭: ', event);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
说明:
event.code
:关闭连接时的状态码。event.reason
:关闭连接的原因。event.wasClean
:是否为干净关闭,即没有数据丢失。
# 4.5 WebSocket.onmessage
用法:onmessage
事件用于在接收到服务器消息时触发回调函数。
// 监听 WebSocket 消息接收事件
ws.onmessage = function (event) {
var data = event.data; // 获取收到的消息数据
console.log('收到的消息: ', data);
};
// 使用 addEventListener 监听消息事件
ws.addEventListener('message', function (event) {
console.log('收到的消息 (addEventListener): ', event.data);
});
2
3
4
5
6
7
8
9
10
说明:接收到的消息可以是文本数据或二进制数据(如 blob
或 ArrayBuffer
)。可以通过 event.data
获取数据,并通过 binaryType
指定数据的格式。
// 动态判断数据类型
ws.onmessage = function(event){
if(typeof event.data === 'string') {
console.log("收到文本数据: " + event.data);
} else if(event.data instanceof ArrayBuffer){
console.log("收到二进制数据: ArrayBuffer");
}
};
// 显式指定二进制数据类型为 blob
ws.binaryType = "blob";
ws.onmessage = function(e) {
console.log("收到 Blob 数据大小: " + e.data.size);
};
// 显式指定二进制数据类型为 ArrayBuffer
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
console.log("收到 ArrayBuffer 数据长度: " + e.data.byteLength);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 4.6 WebSocket.send()
用法:send()
方法用于向服务器发送数据。
// 发送文本消息
ws.send('Hello Server!');
// 发送 Blob 对象
var file = document.querySelector('input[type="file"]').files[0];
ws.send(file);
// 发送 ArrayBuffer 数据
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
ws.send(binary.buffer);
2
3
4
5
6
7
8
9
10
11
12
13
14
说明:send()
方法支持发送多种数据类型,包括字符串、Blob
、ArrayBuffer
等。发送二进制数据时,数据的处理方式取决于 binaryType
属性。
# 4.7 WebSocket.bufferedAmount
用法:bufferedAmount
属性表示还未发送的数据字节数。
// 发送大量数据并检查发送进度
var data = new ArrayBuffer(10000000); // 创建一个 10MB 的数据
ws.send(data);
if (ws.bufferedAmount === 0) {
console.log("数据已全部发送");
} else {
console.log("数据尚未发送完毕,剩余: " + ws.bufferedAmount + " 字节");
}
2
3
4
5
6
7
8
9
说明:当发送的数据量较大时,可以通过 bufferedAmount
检查是否有数据未完全发送。如果该值为 0,表示数据已发送完毕。
# 4.8 WebSocket.onerror
用法:onerror
事件用于在发生错误时触发回调函数。
// 监听 WebSocket 错误事件
ws.onerror = function(event) {
console.error("WebSocket 发生错误: ", event);
};
// 使用 addEventListener 监听错误事件
ws.addEventListener('error', function(event) {
console.error("WebSocket 错误 (addEventListener): ", event);
});
2
3
4
5
6
7
8
9
说明:当 WebSocket 连接发生错误时,会触发 onerror
事件。通常需要在此处理错误信息或进行日志记录。
# 五、服务端的实现
在构建 WebSocket 服务器时,可以选择不同的框架和库。以下是常见的几种 Node.js 实现以及它们的特点和基本用法总结。
# 5.1 µWebSockets
µWebSockets 是一款性能非常高的 WebSocket 库,特别适合高并发和低延迟的应用场景。它以其轻量和高效著称,能够处理数百万的 WebSocket 连接,常用于对性能要求极高的场景。
特点:
- 极高的吞吐量,处理百万级别的连接。
- 轻量化,内存占用低。
- 高效的事件循环机制,适合实时性要求较高的应用。
安装与使用:
npm install uWebSockets.js
基本示例:
const uWS = require('uWebSockets.js');
// 创建一个 WebSocket 服务器
uWS.App().ws('/*', {
// 连接建立时的回调
open: (ws) => {
console.log('客户端连接成功');
ws.send('欢迎使用 µWebSockets!');
},
// 收到消息时的回调
message: (ws, message, isBinary) => {
const msg = Buffer.from(message).toString();
console.log('收到消息:', msg);
ws.send('服务端回复: ' + msg);
},
// 连接关闭时的回调
close: (ws, code, message) => {
console.log('客户端断开连接');
}
}).listen(8080, (token) => {
if (token) {
console.log('WebSocket 服务器正在运行,端口: 8080');
} else {
console.log('服务器启动失败');
}
});
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
# 5.2 Socket.IO
Socket.IO 是最为广泛使用的 WebSocket 库之一。它不仅支持 WebSocket,还兼容 HTTP 长轮询等多种通信协议,具有良好的兼容性和广泛的社区支持,适合需要兼容性和多功能性的应用。
特点:
- 兼容性强,自动选择最佳的通信方式(WebSocket、轮询等)。
- 支持房间、命名空间等功能,适合复杂的实时通信需求。
- 拥有强大的生态系统,插件和中间件丰富。
安装与使用:
npm install socket.io
基本示例:
const io = require('socket.io')(3000);
// 监听客户端连接
io.on('connection', (socket) => {
console.log('客户端连接成功');
// 监听消息
socket.on('message', (msg) => {
console.log('收到消息:', msg);
socket.emit('response', '服务端回复: ' + msg);
});
// 监听断开连接
socket.on('disconnect', () => {
console.log('客户端断开连接');
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5.3 WebSocket-Node
WebSocket-Node 是一个较为简单且符合 WebSocket 标准的实现,适合需要直接操作 WebSocket 协议的场景。它提供了对标准 WebSocket 协议的完整支持,适合开发者根据需要进行高度定制。
特点:
- 符合 WebSocket 标准,灵活性高。
- 轻量且简单,适合进行自定义实现。
- 社区活跃度较高,文档详细。
安装与使用:
npm install websocket
基本示例:
const WebSocketServer = require('websocket').server;
const http = require('http');
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
res.writeHead(404);
res.end();
});
server.listen(8080, () => {
console.log('WebSocket 服务器正在运行,端口: 8080');
});
// 绑定 WebSocket 服务器到 HTTP 服务器
const wsServer = new WebSocketServer({
httpServer: server
});
// 监听 WebSocket 请求
wsServer.on('request', (request) => {
const connection = request.accept(null, request.origin);
console.log('客户端连接成功');
// 监听消息
connection.on('message', (message) => {
if (message.type === 'utf8') {
console.log('收到消息:', message.utf8Data);
connection.sendUTF('服务端回复: ' + message.utf8Data);
}
});
// 监听断开连接
connection.on('close', (reasonCode, description) => {
console.log('客户端断开连接');
});
});
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
# 5.4 各实现的对比与总结
库名称 | 性能 | 兼容性 | 功能丰富度 | 社区支持 | 适用场景 |
---|---|---|---|---|---|
µWebSockets | 极高 | 中等 | 基础 | 一般 | 高性能、高并发的实时应用 |
Socket.IO | 中高 | 极高 | 丰富 | 极高 | 需要兼容性、复杂通信需求的应用 |
WebSocket-Node | 中等 | 标准 WebSocket | 基础 | 一般 | 简单的 WebSocket 应用,需高度自定义的场景 |
总结:选择适合的 WebSocket 实现取决于项目的需求。如果你需要兼容性和丰富的功能,Socket.IO 是首选;如果你追求极致性能,µWebSockets 是更好的选择;而 WebSocket-Node 则适合对协议和功能有精细控制的场景。