传统的 HTTP 协议是无状态的,每一个 HTTP 请求都是独立的,不保存与先前请求相关的信息。因此每次请求都要由客户端主动发起,服务端进行处理后返回结果,而服务端很难主动向客户端发送数据。
这种客户端是主动方,服务端是被动方的传统 Web 模式,对于信息变化不频繁的 Web 应用来说造成的麻烦较小,而对于涉及实时信息的 Web 应用却带来很大的不便,例如带有即时通信、实时数据、订阅推送等功能的应用。
在 Web Socket 规范提出之前,开发人员若要实现这些功能性较强的功能,经常会使用折中的解决方法,例如轮询。轮询是最原始的实现实时 Web 应用的解决方案。在特定的时间间隔,由浏览器对服务器发出 HTTP 请求,然后由服务器返回最新的数据给浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断地向服务器发出请求,然而 HTTP 请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的宽带资源。
Web Socket:
Web Socket 是 HTML5 提供的一种在单个 TCP 连接上进行全双工通讯的应用层协议。在 Web Socket 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
Web Socket 协议是一个基于 TCP 的协议。
创建 Web Socket 对象:
通过 new WebSocke()
向服务器发起一个 HTTP 请求,其中包含附加头信息 "Upgrade:WebSocket"
来表明申请协议升级;服务器解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 Web Socket 连接就建立起来了;双方就可以通过这个连接双向通信,并且这个连接会持续存在直到客户端或服务端的某一方主动关闭连接。
// 第一个参数指定连接的 URL 地址;第二个参数是可选的,指定可接受的子协议
var socket = new WebSocket(url, [protocol] );
Web Socket 的属性:
- readyState:只读属性,表示连接状态。属性值 0 表示连接尚未建立;1 表示连接已建立,可以进行通讯;2 表示连接正在进行关闭;3 表示连接已经关闭或者连接不能打开。
- bufferedAmount:只读属性,已被
send()
放入队列中,等待传输,但是还没有发出的UTF-8
文本字节数。
Web Socket 的方法:
send()
:发送数据。close()
:关闭连接。
Web Socket 的事件:
- open:连接建立时触发。
- message:客户端接收服务端数据时触发。
- error:通信发生错误时触发。
- close:连接关闭时触发。
<a href="javascript:WebSocketTest()">运行 WebSocket</a>
<script type="text/javascript">
function WebSocketTest(){
if ("WebSocket" in window){
// 建立一个WebSocket
var ws = new WebSocket("ws://localhost:9998/echo");
//监听连接建立
ws.onopen = function(){
// 发送数据
ws.send("发送数据");
};
//监听接收数据
ws.onmessage = function (evt) {
var received_msg = evt.data;
};
//监听连接关闭
ws.onclose = function(){
// 关闭 websocket
};
} else{
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
}
</script>
心跳检测和断线重连:
心跳检测和断线重连的目的:用一句话概括就是客户端和服务端保证彼此还活着,避免丢包发生。
在理想状态下,Web Socket 断开时会执行 close 事件,但在异常情况下可能并不会执行,那样的话就无法得知是否断开了连接。因此需要一种机制来检测客户端和服务端是否处于正常连接的状态。每隔一个时间间隔客户端向服务器发送一个心跳消息;服务器接收到消息后,返回一个心跳消息给客户端;客户端可以在 meaasge 事件中接收到服务器的心跳信息,说明连接正常;如果超时还没有收到返回消息,说明连接断开了,执行重连函数。
//Web Socket 实例
let ws;
//连接标识,避免重复连接
let lockReconnect = false;
let timer;
//创建Web Socket
function createWebSocket(){
try{
if ("WebSocket" in window){
// 建立一个WebSocket
ws = new WebSocket("ws://localhost:8080/websocket/testname");
initWebSocket();
}
}catch(e){
//如果因为服务器部署、短暂断网等导致第一时间没有建立连接,那么重新连接!
reconnect();
}
}
//初始化 Web Socket
function initWebSocket(){
//监听连接成功
ws.onopen = function(){
console.log("web socket连接成功");
ws.send();
console.log("web socket发送消息");
heartCheck.start();
}
//监听接收数据
ws.onmessage = function (evt) {
console.log("web socket接收消息:"+ evt.data);
//接收到到任何消息都说明当前连接是正常的,但数据可能是服务端返回的 pong,也可能是正常情况下应该返回的数据,所以需要加上判断
if(evt.data === 'pong'){
}else{
}
heartCheck.start();
};
//监听发生错误
ws.onerror = function(e){
console.log(`web scoket发生错误:${e}`);
//重新连接!
reconnect();
}
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭 Web Socket 连接,防止连接还没断开就关闭窗口导致 server 抛出异常
window.onbeforeunload = function(){
ws.close();
}
//重连函数
function reconnect(){
if(lockReconnect) {
return false;
}
lockReconnect = true;
//没连接上会一直重连,设置函数防抖避免请求过多
timer && clearTimeout(timer);
timer = setTimeout(function () {
createWebSocket();
lockReconnect = false;
}, 5000);
}
//心跳检测
var heartCheck = {
//发送心跳包的间隔时间
timeout: 30000,
timeoutObj: null,
//服务器端的超时时间
severTimeout: 120000,
severTimeoutObj: null,
//设置函数防抖避免一直重复发送心跳
start: function() {
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(()=>{
ws.send("ping"); // 心跳包
//连接正常的情况下,心跳包每隔30000ms发送一次,也就是说,每隔30000ms,serverTimeoutObj这个服务器端的延时120000ms的函数将会被清除一次,永远无法执行;
//连接异常的情况下,才不会被清除而执行;
this.serverTimeoutObj = setTimeout(()=>{
reconnect();
}, this.severTimeout);
}, this.timeout)
}
}