简介
WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
发展历程
WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。
- HTTP1.0:一个HTTP请求的生命周期:客户端发起,服务端响应,断开连接。
- 缺点:无法复用、三次握手、慢启动
- HTTP1.1:管道化,默认开启长链接(Connection:keep-alive),服务器处理后不会立即关闭连接会等待。
- 缺点:无法区分回包对应请求,顺序返回导致线头阻塞;不支持服务端推送
- HTTP2.0:多路复用。实现服务器推送,使用帧、流等方式解决了线头阻塞。HTTP2.0在一个tcp连接中,可以同时发送多个HTTP请求,每个请求是一个流,一个流可以分成很多帧,有了标记号,服务器可以随便发送回包,客户端收到后,根据标记,重新组装就可以。
以上是关于HTTP协议关于请求的一些发展,而WebSocket就服务端推送提供了另一种解决方案-双向通信协议。
解决了哪些问题?
解决了HTTP的几个难题:
- 服务端可以主动向客户端推送
- 双向通讯
- 高效连接
- 节约带宽
- 节省服务器资源
特点及作用
特点
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
作用
- 解决服务端长时间处理等待超时问题
- 解决获取服务端实时状态无法高效推送问题
- 解决高频推送建立链接高耗时问题
与其他技术比对
- Ajax轮询 让浏览器隔个几秒就发送一次请求,询问服务端有没有新消息。 需要服务器有很快的处理速度和资源
- 长轮询 也采用轮询的方式,不过采取的是阻塞模型,直到返回Response后,再次建立连接。 需要有很高的并发能力
- WebSocket 一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,避免了HTTP的非状态性
优缺点
- 优点
- WebSocket是一种双向通讯协议
- 实时应用都在使用WebSocket在单个通道上接收数据
- 经常更新的应用程序都应该使用WebSocket,比HTTP更快
- 是HTML5的技术之一,具有巨大的应用前景
- 缺点
- 不兼容低版本的IE
- 要获取旧数据,或者只想获取一次数据供应用程序使用,则应该使用HTTP协议
- 如果仅加载一次数据,则RestFul Web服务足以从服务器获取数据。
核心原理
WebSocket 协议
WebSocket协议是通过HTTP协议升级而来。只需要在HTTP协议基础上增加两次握手,即可建立WebSocket连接。本质上是在TCP协议上封装的另一种应用层协议(WebSocket协议)。因为他是基于TCP的,所以服务端可以自己推送。协议升级过程如下:
- 客户端发送协议升级的请求。在HTTP请求加上,如图一的HTTP头。
- 服务器如果支持WebSocket协议的话,会返回101状态码表示同意协议升级,并且支持各种配置(如果服务器不支持某些功能或版本,或告诉客户端,客户端可以再次发送协议升级的请求)。服务会返回形如图二的HTTP头。
- 这样就完成了协议的升级,后续的数据通信,就是基于tcp连接之上,使用WebSocket协议封装的数据包。
【图一:客户端发送协议升级】
【图二:服务端同意协议升级】
技术应用
- 实时推送订单结果消息
- 文件上传解析结果通知
- websocket社交订阅
- websocket多玩家游戏
- websocket协同编辑/编程
- websocket收集点击流数据
- 股票基金报价
- 体育实况更新
- 多媒体聊天
- 在线教育
- 基于位置的应用
- 论坛的消息广播
组件封装
封装单例组件,并支持多个服务端webSocket连接。
// import handleMsgMap from './handleReceiveMessage';
let websocketObj = {};
const newWebSocket = async (url, openCallback, messageCallback, closeCallback) => {
console.log('new webSocket.....', websocketObj[url]);
if (websocketObj[url]) return websocketObj[url];
// 判断当前环境是否支持websocket
if (window.WebSocket) {
// wss: https协议使用,ws:http协议使用
websocketObj[url] = new WebSocket(url);
// 连接成功建立的回调方法
websocketObj[url].onopen = function () {
// 成功建立连接后,重置心跳检测
console.log('websocket connect');
if (openCallback) {
openCallback(websocketObj[url]);
} else {
const content = JSON.stringify({ content: '你好啊!', type: '0' });
websocketObj[url].send(content);
}
// heartCheck.reset().start(url, openCallback, messageCallback, closeCallback);
console.log('connected successfully');
};
// 接受到消息的回调方法
websocketObj[url].onmessage = function (e) {
const message = JSON.parse(e.data);
const { isSuccess, data, msgType, respCode } = message;
if (respCode && handleRespCodeStrategy[respCode]) {
handleRespCodeStrategy[respCode](url, openCallback, messageCallback, closeCallback);
return;
}
if (messageCallback) {
messageCallback(e);
return;
}
if (isSuccess && data) {
// 执行接收到消息的操作,分发actions执行操作
handleMsgMap[msgType](data);
}
};
// 接受到服务端关闭连接时的回调方法
websocketObj[url].onclose = function (e) {
console.log('service onclose');
closeCallback && closeCallback(e);
};
// 连接发生错误,连接错误时会继续尝试发起连接,间隔30s
websocketObj[url].onerror = function () {
closeWebsocket(url);
setTimeout(() => {
newWebSocket(url, openCallback, messageCallback, closeCallback);
}, 30000);
};
// 监听窗口事件,当窗口关闭时,主动断开websocket连接,防止连接没断开就关闭窗口,server端报错
window.onbeforeunload = () => {
console.log('window onclose');
return websocketObj[url] && websocketObj[url].close();
};
return websocketObj[url];
} else {
console.log('not support websocket');
return null;
}
};
// 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
const heartCheck = {
timeout: 10000, // 心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
serverTimeoutObj: null,
reset: function () {
clearInterval(this.serverTimeoutObj);
this.serverTimeoutObj = null;
return this;
},
start: function (url, openCallback, messageCallback, closeCallback) {
this.serverTimeoutObj = setInterval(() => {
if (websocketObj[url].readyState === 1) {
const content = JSON.stringify({ msgType: 1 });
websocketObj[url].send(content);
} else {
closeWebsocket(url);
newWebSocket(url, openCallback, messageCallback, closeCallback);
}
}, this.timeout);
}
};
// 处理无网状态 断开重连机制
window.addEventListener('online', () => {
if (!websocketObj[url]) {
newWebSocket(url, openCallback, messageCallback, closeCallback);
}
});
window.addEventListener('offline', () => {
if (websocketObj[url]) {
closeWebsocket(url);
}
});
const closeWebsocket = (url) => {
heartCheck.reset();
websocketObj[url] && websocketObj[url].close();
websocketObj[url] = null;
};
// 1001:重新连接 1002:关闭心跳定时器 1003:重新登录 跳转login 1004:关闭websocket 9999:失败保持不动
const handleRespCodeStrategy = {
1001: function (url, openCallback, messageCallback, closeCallback) {
websocketObj[url] && closeWebsocket(url) && newWebSocket(url, openCallback, messageCallback, closeCallback);
},
1002: function () {
heartCheck.reset();
},
1003: function (url) {
closeWebsocket(url);
window.location.href = window.location.origin + window.location.pathname + '#/login'; // 被挤掉时,有弹窗存在,处理弹窗问题
window.location.reload();
},
1004: function (url) {
closeWebsocket(url);
},
9999: () => {}
};
// export default newWebSocket;