> 技术文档 > WebSocket详解

WebSocket详解


一.什么是 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;