一、前言
在我们了解websocket之前,不妨先想想这几个问题:
- websocket是什么?
- websocket有什么好处和特点?
- 为什么要用到websocket?
- 什么情况下会用到websocket?
好了,带着这几个疑问一起来了解一下websocket。
二、websocket简介
2.1 什么是websocket
WebSocket是HTML5开始提供的一种基于TCP的协议,用于在客户端和服务器之间建立持久连接,实现实时通讯的功能。
早期,客户端如果想实时拿到浏览器的最新数据就必须要通过发送http请求定时做轮询。每隔一段时间去向浏览器请求最新数据,这样大大的消耗了服务器的资源。
当使用了websocket与服务器建立连接,就不需要反复去轮询请求数据;当有最新消息时服务器会返回给客户端,而websocket可以立马监听到服务器的返回消息。
2.2 websocket的特点
2.2.1 优点
- 可以说实现连接的持久化,使客户端与服务器可以实时双向通讯;
- 减少了HTTP请求,大大减少了服务器的资源消耗,降低了网络负载;
- 没有同源限制,客户端可以与任意服务器通信;
- 协议标识符为ws,如果是加密的话就是wss;
2.2.2 缺点
- 有兼容问题,有一些老版本的浏览器不支持,同时有一些网络代理或防火墙会阻止连接;
- 缺乏安全策略,前面讲过没有同源策略的限制(这个需要注意);
- 对网络要求比较高,如果网络波动频繁可以回导致经常断联(导致断联原因不好排查);
- 需要保持长连接也会占用服务器的较多资源;
- 不适合大文件传输,websocket协议发送数据包不能超过2GB;
2.3 使用场景
websocket的主要优点就是保持客户端与服务端双向实时通讯,所以他主要的引用场景在于:
- 在线聊天室、游戏消息推送;
- 多媒体对话,例如视频会议等;
- 网页更新实时的数据流,股票价格波动、票房分析等;
三、websocket对象详解
WebSocket
对象提供了用于创建和管理 WebSocket]连接,以及可以通过该连接发送和接收数据的 API。
3.1 实例属性
通过使用WebSocket()构造函数来实例化一个WebSocket对象。
3.1.1 url
Websocket的绝对路径(只读)
3.1.2 readyState
WebSocket当前连接的状态(只读)
- WebSocket.CONNECTING:正在连接中
- WebSocket.OPEN:已经连接可以通信
- WebSocket.CLOSING:连接正在关闭
- WebSocket.CLOSED:连接已关闭或者没有连接成功
3.1.3 protocol
protocol是个只读属性,用于返回服务器端选中的子协议的名字;这是一个在创建 WebSocket
对象时,在参数 protocols
中指定的字符串,当没有已建立的链接时为空串。
3.2 实例方法
3.2.1 close()
WebSocket.close()
方法关闭 WebSocket
连接或连接尝试(如果有的话)。如果连接已经关闭,则此方法不执行任何操作。
3.2.2 send()
WebSocket.send()
方法将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的 data bytes 的大小来增加 bufferedAmount
的值。若数据无法传输(例如数据需要缓存而缓冲区已满)时,套接字会自行关闭。
3.3 事件监听回调
3.3.1 close
监听将在 WebSocket 连接的readyState
变为 CLOSED
时被调用
3.3.2 error
当websocket
的连接由于一些错误事件的发生 (例如无法发送一些数据) 而被关闭时,一个error
事件将被引发。
3.3.3 message
message
事件会在 WebSocket
接收到新消息时被触发。
3.3.4 open
监听将在 WebSocket 连接的readyState
变为 CONNECTING
时被调用
四、封装WebSocket工具函数
export class WebSocketClient {url = '';socket = null;// #计时器idheartbeatTimer = undefined;// 是否彻底关闭stopWebSocket = false;// 消息列表msgList = [];// 事件监听对象eventListenerInfo = {};// 构造函数constructor(url) {this.url = url;}// 添加事件监听addEventListener(type, listener) {if (!this.eventListenerInfo[type]) {this.eventListenerInfo[type] = [];}if (this.eventListenerInfo[type].indexOf(listener) === -1) {this.eventListenerInfo[type].push(listener);}}// 删除事件监听removeEventListener(type) {this.eventListenerInfo[type] = [];}// 监听事件绑定dispatchEvent(type, data) {const listenerArray = this.eventListenerInfo[type] || [];if (listenerArray.length === 0) return;listenerArray.forEach(listener => {listener.call(this, data);});}// 监听回调onopen(callBack) {this.addEventListener('open', callBack);}onmessage(callBack) {this.addEventListener('message', callBack);}onclose(callBack) {this.addEventListener('close', callBack);}onerror(callBack) {this.addEventListener('error', callBack);}// 消息列表增加addMsgList(list) {this.msgList = [...this.msgList, ...list];}// 批量发送消息pushMsgListAll() {if (this.msgList.length && this.socket && this.socket.readyState === WebSocket.OPEN) {// 发送头部数据const msg = this.msgList.shift();this.send(msg);pushMsgListAll();} else {// 重连重新发送this.connect();}}// 消息发送send(message) {if (this.socket && this.socket.readyState === WebSocket.OPEN) {this.socket.send(message);} else {console.error('websocket 未连接或已断开,请查看!');}}// 心跳检测onHeartbeat() {if (this.stopWebSocket) return;if (this.heartbeatTimer) {this.offHeartbeat();}this.heartbeatTimer = setInterval(() => {if (this.socket) {this.socket.send('ping,测试是否连接中');} else {console.error('websocket 未连接或已断开,请查看!');}}, 10000);}// 关闭心跳检测offHeartbeat() {clearInterval(this.heartbeatTimer);this.heartbeatTimer = undefined;}// 连接connect() {console.log(`WebSocket连接地址: ${this.url}`);if (this.socket && this.socket.readyState === WebSocket.OPEN) {return;}this.socket = new WebSocket(this.url);// !websocket连接成功this.socket.onopen = event => {this.stopWebSocket = false;// 开启心跳检测this.onHeartbeat();console.log(`连接成功`);// 判断是否有消息未发送成功if (this.msgList.length) {this.pushMsgListAll();}this.dispatchEvent('open', event);};this.socket.onmessage = event => {this.dispatchEvent('message', event);this.onHeartbeat();};this.socket.onclose = event => {if (!this.stopWebSocket) {// 断网重连逻辑setTimeout(() => {this.connect();}, 5000);}this.dispatchEvent('close', event);};this.socket.onerror = event => {this.offHeartbeat();this.dispatchEvent('error', event);};}// 关闭连接close() {if (this.socket) {this.stopWebSocket = true;this.socket.close();this.socket = null;this.removeEventListener('open');this.removeEventListener('message');this.removeEventListener('close');this.removeEventListener('error');}this.offHeartbeat();}
}