一.什么是 WebSocket?
WebSocket 是一种在单个 TCP 连接上进行全双工(双向)通信的协议。它是为了弥补传统 HTTP 协议在实时通信场景中的不足而设计的,允许浏览器和服务器之间进行低延迟的实时数据传输
二.WebSocket的优势
WebSocket 与 HTTP 的区别
特性 | WebSocket | HTTP |
---|---|---|
协议类型 | 双向通信协议 | 请求-响应协议 |
连接状态 | 持久连接 | 每个请求都需要建立新连接 |
数据传输方式 | 双向全双工通信 | 客户端发起请求,服务器响应 |
连接开销 | 低开销(连接一次即可) | 每个请求都需要建立新的连接 |
实时性 | 实时,适合长时间的数据交换 | 每次请求/响应有延迟,适合短期通信 |
适用场景 | 实时聊天、在线游戏、推送通知 | 静态网页加载、表单提交等 |
我们再来看看与其他实时通讯技术的对比
通信方式 | 描述 | 优点 | 缺点 |
---|---|---|---|
轮询 | 客户端以固定时间间隔(如每秒)向服务器发送 HTTP 请求,询问是否有新数据。每次请求都需要建立新连接。 | - 简单易实现 | - 延迟高:每次请求都需要建立新连接,延迟较大。 - 带宽浪费:频繁发送无效请求。 - 服务器负担大:每个请求都需要重新建立连接并处理。 |
长轮询 | 客户端向服务器发送请求,服务器保持连接,直到有新数据时才响应客户端。减少了连接的频繁建立。 | - 减少连接建立开销:相较于普通轮询,减少了频繁的连接请求。 - 更低的延迟:由于连接持续存在。 |
- 仍然存在延迟:客户端必须等待服务器推送新数据。 - 资源消耗大:等待响应之后再关闭,还是有重复连接 |
SSE | 服务器单向推送数据到客户端,支持实时数据流,但只支持单向通信。 | - 实时推送:服务器可以随时推送数据给客户端。 - 实现简单:只需服务器推送数据。 |
- 单向通信:不支持客户端向服务器发送数据。 - 不适合双向实时应用:无法进行双向数据交互。 - 浏览器兼容性差:部分浏览器不完全支持 SSE。 |
三.WebSocket的属性和方法
WebSocket 属性
属性 | 描述 | 示例 |
---|---|---|
url |
连接的目标 URL。 | new WebSocket('wss://example.com'); |
readyState |
返回 WebSocket 连接的当前状态:0 (连接中)、1 (已连接)、2 (关闭中)、3 (已关闭)。 |
console.log(socket.readyState); |
bufferedAmount |
当前缓冲区中待发送的数据大小(字节)。 | console.log(socket.bufferedAmount); |
protocol |
连接时使用的子协议(如果有)。 | console.log(socket.protocol); |
extensions |
返回连接时使用的扩展协议(如果有)。 | console.log(socket.extensions); |
WebSocket 方法
方法 | 描述 | 示例 |
---|---|---|
send() |
向服务器发送消息(文本或二进制)。 | socket.send("Hello, Server!"); |
close() |
关闭 WebSocket 连接。 | socket.close(); |
WebSocket 事件
事件 | 描述 | 触发时机 |
---|---|---|
onopen |
连接成功建立时触发。 | 连接成功后触发 |
onmessage |
接收到服务器消息时触发。 | 当服务器发送消息时触发 |
onclose |
连接关闭时触发。 | 连接被关闭时触发 |
onerror |
发生错误时触发。 | 连接发生错误时触发 |
WebSocket 连接状态常量(readyState
)
状态值 | 描述 |
---|---|
0 (CONNECTING) |
连接正在建立中。 |
1 (OPEN) |
连接已成功建立,可以进行数据交换。 |
2 (CLOSING) |
连接正在关闭。 |
3 (CLOSED) |
连接已关闭或无法建立连接。 |
实例:
function webSocketManager(options) {
if (!options.url) {
throw new Error("url必传");
}
if (!options.bizMessageHandler) {
throw new Error("bizMessageHandler必传");
}
this.url = options.url;
this.heartbeatInterval = options.heartbeatInterval || 10; // 心跳间隔,单位:秒
this.heartbeatStopCount = options.heartbeatStopCount || 3; // 心跳几次没响应就停止,默认3次
this.autoReconnect = options.autoReconnect || true; // 断开后是否自动重连
this.reconnectMinDuration = options.reconnectMinDuration || 10; // 重连最小间隔时间,单位:秒
this.bizMessageHandler = options.bizMessageHandler; // 业务消息处理函数
this.onConnectionOpen = options.onConnectionOpen; // 建立连接时回调
}
webSocketManager.prototype.init = function() {
this._connect();
};
webSocketManager.prototype._startHeartbeat = function() {
let _this = this;
this._log("start heartbeat");
this.lastSendSeq = 0; // 最后一次发出的序列号
this.lastReceiveSeq = 0; // 最后一次收到的序列号
this.webSocket.addEventListener("message", function(e) {
let data = parseInt(e.data);
if (!isNaN(data)) {
_this.lastReceiveSeq = data;
}
});
this.heartbeatTimerId = window.setInterval(function() {
let noRespCount = _this.lastSendSeq - _this.lastReceiveSeq;
if (noRespCount >= _this.heartbeatStopCount) {
_this._log(`心跳包未响应超过${_this.heartbeatStopCount}次, 主动断开连接`);
_this.webSocket.close();
_this._stopHeartbeat();
return;
}
_this.webSocket.send(_this.lastSendSeq++);
}, this.heartbeatInterval * 1000);
};
webSocketManager.prototype._stopHeartbeat = function() {
if (null != this.heartbeatTimerId) {
this._log("stop heartbeat");
window.clearInterval(this.heartbeatTimerId);
this.heartbeatTimerId = null;
}
};
// 连接建立
webSocketManager.prototype._connect = function() {
let _this = this;
this.webSocket = new WebSocket(this.url);
this.connectTime = new Date().getTime();
// 连接建立成功
this.webSocket.onopen = function() {
_this._log("onopen");
if (_this.onConnectionOpen) {
_this.onConnectionOpen();
}
_this._startHeartbeat();
};
// 连接建立失败
this.webSocket.onerror = function() {
_this._log("onerror");
};
// 连接断开
this.webSocket.onclose = function() {
_this._log("onclose");
_this._stopHeartbeat();
// 如果设置为自动重连就自动重连
if (_this.autoReconnect) {
// 为了避免断网时频繁不停重连的情况,需要判断下上次连接时间
let duration = new Date().getTime() - _this.connectTime;
// 如果重连小于最小间隔的时间,则延迟再重连,否则马上重连
if (duration < _this.reconnectMinDuration * 1000) {
setTimeout(function() {
_this._connect();
}, _this.reconnectMinDuration * 1000 - duration);
} else {
_this._connect();
}
}
};
this.webSocket.addEventListener("message", function(e) {
let msg = e.data;
// _this._log(`onmessage; message is ${msg}`);
// 如果是业务消息则回调业务消息处理函数
if (_this._isBizMessage(msg)) {
_this.bizMessageHandler(msg);
}
});
};
webSocketManager.prototype._isBizMessage = function(msg) {
return msg && msg.indexOf("{") === 0;
};
webSocketManager.prototype._log = function(msg) {
if (console && console.log) {
console.log(`[webSocketManager] ${new Date().getTime()} ${msg}`);
}
};
export default webSocketManager;