是什么
WebSocket,是一种网络传输协议,位于OSI
模型的应用层。可在单个TCP
连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅
基于一个已经建立的 TCP 连接之上,通过一个特殊的 HTTP 请求来实现"协议升级"。客户端和服务器需要再完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。不需要客户端再去请求数据,服务器可以主动向客户端发送数据。
在websocket出现之前,在 WebSocket 协议出现以前,创建一个和服务端进双通道通信的 web 应用,需要依赖HTTP协议,进行不停的轮询,HTTP 协议有一个缺陷:通信只能由客户端发起,不具备服务器推送能力。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。如果轮询的频率比较高,那么就可以近似地实现"实时通信"的效果。轮询的缺点也很明显,反复发送无效查询请求耗费了大量的带宽和 CPU资源
特点
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
WebSocket 的其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws
(如果加密,则为wss
),服务器网址就是 URL
优缺点
优点:
- 更强的实时性:WebSocket 提供了低延迟的实时通信能力,能够在服务器端有新数据时立即推送给客户端
- 减少网络负载:由于websocket的持久化连接,他减少了http请求的发送次数,减少了网络负载
- 保持创连接状态:创建通信后,可省略状态信息,不同于HTTP每次请求需要携带身份验证
- 更高的性能:由于减少了 HTTP 请求的开销,WebSocket 在性能上更高效。
- 跨域支持:WebSocket 具备跨域通信的能力,可以跨域进行实时通信。
缺点:
- WebSocket需要浏览器和服务器端都支持该协议。
- WebSocket会增加服务器的负担,不适合大规模连接的应用场景。
- 不需要很频繁或仅获取一次的数据可以通过简单的HTTP请求查询,因此在这种情况下最好不要使用WebSocket。
应用场景
- 弹幕
- 在线游戏
- 媒体聊天
- 协同编辑
- 基于位置的应用
- 体育实况更新
- 股票基金报价实时更新
WebSocket 的连接建立过程是怎样的?
-
客户端发起http请求,经过3次握手后,建立起TCP连接;http请求头里存放如:Upgrade、Connection、WebSocket-Version等;
arduinoUpgrade: websocket Connection: Upgrade //指定协议升级和建立连接 //告诉服务器"我希望升级到 WebSocket"。
makefileSec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
Sec-WebSocket-Key
是一个 Base64 encode 的值,这个是浏览器随机生成的,验证服务器是不是真的是 WebSocket 助理。Sec-WebSocket-Version
告诉服务器所使用的协议版本Sec-WebSocket-Protocol
客户端指定它想要使用的 WebSocket 子协议
💡WebSocket 本身定义了一个通用的通信协议,但它并不规定具体应用层的数据格式或交互逻辑。这意味着,在不同的应用场景下,你可能需要使用不同的协议来解释 WebSocket 传输的数据。
例如:
- 在线聊天应用: 可能需要一个处理消息、用户状态、通知等功能的协议。
- 实时游戏: 可能需要一个传输玩家动作、游戏状态、同步信息的协议。
- 特定设备控制: 可能需要一个定义设备命令和响应的协议。
-
然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;验证请求头的字段,并返回握手响应,响应头包含 Upgrade 和 Connection 字段,以及一个随机的 Sec-WebSocket-Key 字段。
-
客户端收到握手响应后,验证响应头的字段,并生成一个 Sec-WebSocket-Accept 值进行验证。
-
验证通过后,WebSocket 连接建立成功,客户端和服务器基于TCP进行实时全双工通信。
WebSocket 的事件有哪些?请分别描述它们的作用
open
:当 WebSocket 连接成功建立时触发的事件。可以在此事件中执行初始化操作或向服务器发送初始数据。message
:当从服务器接收到新消息时触发的事件。可以在此事件中处理接收到的数据。error
:当出现连接错误时触发的事件。错误可能包括连接失败、数据传输错误等。可以在此事件中处理错误并采取适当的措施。close
:当 WebSocket 连接关闭时触发的事件。关闭可能是由服务器或客户端发起的,可以在此事件中执行清理操作或重新连接等操作。
在浏览器端如何创建和使用 WebSocket 对象?
在浏览器端,可以使用 JavaScript 中的 WebSocket
对象来创建和使用 WebSocket。示例代码如下:
js
const socket = new WebSocket('wss://example.com/socket');
其中,new WebSocket()
通过传入服务器的 WebSocket URL 来创建一个 WebSocket 对象。然后可以通过设置事件处理函数来处理 WebSocket 的事件,例如:
js
socket.onopen = function(event) {
console.log('WebSocket 连接已打开');
};
socket.onmessage = function(event) {
const message = event.data;
console.log('接收到消息:', message);
};
socket.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
socket.onclose = function(event) {
console.log('WebSocket 连接已关闭');
};
在连接建立成功后,可以使用 send()
方法发送消息到服务器,例如:
js
socket.send('Hello, server!');
心跳机制
一个健壮的前端 WebSocket 连接管理通常是以下心跳机制和重连机制的结合
前端实现WebSocket心跳机制的方式主要有两种:
-
使用setInterval定时发送心跳包。
-
WebSocket心跳包机制
WebSocket心跳包是WebSocket协议的保活机制,用于维持长连接。有效的心跳包可以防止长时间不通讯时,WebSocket自动断开连接。
心跳包是指在一定时间间隔内,WebSocket发送的空数据包。常见的WebSocket心跳包机制如下:
- 客户端定时向服务器发送心跳数据包,以保持长连接。
- 服务器定时向客户端发送心跳数据包,以检测客户端连接是否正常。
- 双向发送心跳数据包。
-
-
在前端监听到WebSocket的onclose()事件时,重新创建WebSocket连接。
-
WebSocket重连机制
WebSocket在发送和接收数据时,可能会因为网络原因、服务器宕机等因素而断开连接,此时需要使用WebSocket重连机制进行重新连接。
WebSocket重连机制可以通过以下几种方式实现:
- 前端监听WebSocket的onclose()事件,重新创建WebSocket连接。
- 使用WebSocket插件或库,例如Sockjs、Stompjs等。
- 使用心跳机制检测WebSocket连接状态,自动重连。
- 使用断线重连插件或库,例如ReconnectingWebSocket等。
-
第一种方式会对服务器造成很大的压力,因为即使WebSocket连接正常,也要定时发送心跳包,从而消耗服务器资源。第二种方式虽然减轻了服务器的负担,但是在重连时可能会丢失一些数据。
下面是心跳机制具体实现代码: start 函数负责定期(由 timeout 控制)发送"hello"消息。reset 函数在收到服务器确认消息("收到,hello")时被调用,用来刷新这个心跳计时器。
当连接成功打开 (onopen) 时,会重置重连次数并启动心跳。当收到特定服务器消息 (status === 408) 时,会触发重连 (reconnect)。
js
// 开启心跳
const start = () => {
clearTimeout(timeoutObj);
// serverTimeoutObj && clearTimeout(serverTimeoutObj);
timeoutObj = setTimeout(function () {
if (websocketRef.current?.readyState === 1) {
//连接正常
sendMessage('hello');
}
}, timeout);
};
const reset = () => {
// 重置心跳 清除时间
clearTimeout(timeoutObj);
// 重启心跳
start();
};
ws.onopen = (event) => {
onOpenRef.current?.(event, ws);
reconnectTimesRef.current = 0;
start(); // 开启心跳
setReadyState(ws.readyState || ReadyState.Open);
};
ws.onmessage = (message: WebSocketEventMap['message']) => {
const { data } = message;
if (data === '收到,hello') {
reset();
return;
}
if (JSON.parse(data).status === 408) {
reconnect();
return;
}
onMessageRef.current?.(message, ws);
setLatestMessage(message);
};
const connect = () => {
reconnectTimesRef.current = 0;
connectWs();
};
websocket 断线重连
-
如何判断在线离线?
当客户端第一次发送请求至服务端时会携带唯一标识、以及时间戳,服务端到db或者缓存去查询改请求的唯一标识,如果不存在就存入db或者缓存中,
第二次客户端定时再次发送请求依旧携带唯一标识、以及时间戳,服务端到db或者缓存去查询改请求的唯一标识,如果存在就把上次的时间戳拿取出来,使用当前时间戳减去上次的时间,
得出的毫秒秒数判断是否大于指定的时间,若小于的话就是在线,否则就是离线
-
如何解决断线问题
断线的可能原因1:websocket超时没有消息自动断开连接
这时候我们就需要知道服务端设置的超时时长是多少,在小于超时时间内发送心跳包(客户端主动发送上行心跳包,或者服务端主动发送下行心跳包)
下面主要讲一下客户端也就是前端如何实现心跳包:
首先了解一下心跳包机制
跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
心跳检测步骤:
- 客户端每隔一个时间间隔发生一个探测包给服务器
- 客户端发包时启动一个超时定时器
- 服务器端接收到检测包,应该回应一个包
- 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
- 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
js// 前端解决方案:心跳检测 var heartCheck = { timeout: 30000, //30秒发一次心跳 timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ //这里发送一个心跳,后端收到后,返回一个心跳消息, //onmessage拿到返回的心跳就说明连接正常 ws.send("ping"); console.log("ping!") self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了 ws.close(); //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次 }, self.timeout); }, this.timeout); } }
断线的可能原因2:websocket异常包括服务端出现中断,交互切屏等等客户端异常中断等等
**异常情况的可能原因:**服务器本身出现问题/在移动设备上切换应用、返回桌面,或者在浏览器中关闭标签页、最小化窗口等操作
- 客户端的处理: "客户端需要主动断开连接,并通过
onclose
事件来关闭连接。"onclose
事件通常会在 WebSocket 连接被终止时触发。 - 服务器的应对(重新上线时): "服务器再次上线时,则需要清除(之前因宕机而)存放的(与客户端相关的)数据。" 这用于维护数据的准确性和防止状态管理错误。
- 不清除数据的后果: "如果不清除这些数据,就会造成任何(客户端)请求到服务器的都会被视为离线。" 这意味着,如果服务器没有重置它在连接中断期间存储的与客户端相关的状态信息,它可能会错误地认为客户端仍然处于离线状态或处于一个无效的状态,
针对这种异常的中断解决方案就是处理重连,下面我们给出的重连方案是使用js库处理:引入reconnecting-websocket.min.js,ws建立链接方法使用js库api方法:
jsvar ws = new ReconnectingWebSocket(url); // 断线重连:reconnectSocket(){ if ('ws' in window) { ws = new ReconnectingWebSocket(url); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(url); } else { ws = new SockJS(url); }
断网监测支持使用js库:offline.min.js
js
onLineCheck(){
Offline.check();
console.log(Offline.state,'---Offline.state');
console.log(this.socketStatus,'---this.socketStatus');
if(!this.socketStatus){
console.log('网络连接已断开!');
if(Offline.state === 'up' && websocket.reconnectAttempts > websocket.maxReconnectInterval){
window.location.reload();
}
reconnectSocket();
}else{
console.log('网络连接成功!');
websocket.send("heartBeat");
}
}
// 使用:在websocket断开链接时调用网络中断监测websocket.onclose => () {
onLineCheck();
};
引用
面试官:你知道websocket的心跳机制吗?大家好,我是泽南Zn👨🎓。在之前的一篇文章写到, 前端如何使用web - 掘金