一、定义
WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信(又称为双向同时通信,即通信的双方可以同时发送和接受信息的信息交互方式),与HTTP协议同属于应用层协议。
二、背景
传统模式实现推送所用技术为轮询,即在特定的时间间隔中,浏览器对服务器发送送HTTP请求,服务器返回最新的数据给客户端浏览器。这样有一个明显的缺点,HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,这样会消耗很多带宽资源。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次HTTP握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
三、特点
- 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话
- 建立在TCP协议之上,服务器端的实现比较容易
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效
- 可以发送文本和二进制数据
- 协议标识符是ws(加密的话是wss),服务器网址(要通信的地址)就是url,可以像http类型地址一样增加参数,在后面增加“?参数名=参数值”即可。eg:ws://example.com:80/some/path?token=123
- 没有同源限制,客户端可以与任意服务器通信
同源限制:指的是三个相同即协议相同、域名相同、端口相同。限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
四、网络七层协议
网络七层协议从上到下依次为:
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理层
其中前四层定义了应用程序的功能,后三层主要面向通过网络的端到端的数据流
五、WebSocket数据处理
WebSocket是通过事件通知的方式运行的。它包含四个事件和两个动作(发送和关闭
主要属性(事件)
- onopen:建立连接时触发
- onmessage:客户端接收服务端数据时触发
- onerror:通信发生错误时触发
- onclose:连接关闭时触发
方法(动作)
- close:关闭当前链接
- send:向服务器发送数据
使用
创建一个链接
浏览器发起的ws请求,可以看到和HTTP请求长的是非常相似的。但是,它只是请求阶段长得像而已,下面是一个HTTP请求头和一个WebSocket请求头:
HTTP请求头.png WebSocket请求头.png请求的地址,一般是:ws://***,或者是使用了SSL/TLS加密的安全协议wss:,用来标识是WebSocket请求。
- 首先,通过Http头里面的Upgrade域,请求进行协议转换。如果服务端支持的话,就可以切换到WebSocket协议。简单点讲:连接已经在那了,通过握手切换成ws协议,就是切换了连接的一个状态而已。
- Connection域可以认为是与Upgrade域配对的头信息。像nginx等代理服务器,是要先处理Connection,然后再发起协议转换的。
- Sec-WebSocket-Key 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。如此操作,可以尽量避免普通 HTTP 请求被误认为 WebSocket 协议。
- 其他的,像Sec-WebSocket*字样的头信息,表明了客户端支持的子协议以及其他信息。
断连问题
客户端与服务器端连接后,一旦长时间没有通信,当服务器资源紧张的时候会优先断开websocket不活跃的连接,此时需要做一个心跳检测,使得客户端与服务器端能正常保持着连接状态,即便在断网等外在因素影响下断开后也能正常保持连接状态
interface HeartCheckInfo {
timeout: number;
timeoutObj: number | undefined;
serverTimeoutObj: number | undefined;
start: () => void;
}
let ws: WebSocket | null = null;
let url: string = '';
let isConnect: boolean = false;
let conTimer: number | undefined = undefined;
let handleData: (data: string) => void = () => {};
const createWebSocket = (wsUrl: string, callback: (data: string) => void) => {
url = wsUrl;
handleData = callback;
try {
if ('WebSocket' in window) {
ws = new WebSocket(url);
} else {
// 提示不支持websocket
console.log('不支持websocket')
}
handleEvent();
} catch(e) {
reconnect();
console.log(e);
}
}
const handleEvent = () => {
if (ws) {
ws.onclose = () => {
reconnect();
// console.log('连接关闭');
};
ws.onerror = () => {
reconnect();
// console.log('连接错误');
};
ws.onopen = () => {
// 重置心跳值
heartCheck.start();
console.log('开始接收消息');
};
ws.onmessage = (event) => {
// 重置心跳值
heartCheck.start();
const { data } = event
handleData(data);
}
}
}
const reconnect = () => {
if (isConnect) return;
isConnect = true;
//没连接上会一直重连,设置延迟避免请求过多
conTimer && clearTimeout(conTimer);
conTimer = window.setTimeout(() => {
createWebSocket(url, handleData);
isConnect = false;
}, 2000);
}
const heartCheck: HeartCheckInfo = {
timeout: 30001,
timeoutObj: undefined,
serverTimeoutObj: undefined,
start(){
const self = this;
self.timeoutObj && clearTimeout(self.timeoutObj);
self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
self.timeoutObj = window.setTimeout(() => {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
ws && ws.send('心跳值');
// 服务器关闭
self.serverTimeoutObj = window.setTimeout(() => {
console.log(ws);
ws && ws.close();
}, self.timeout);
}, self.timeout)
}
}
Q&A
Socket与WebSocket是否相等?
Socket !== WebSocket
- Socket:套接字,有目标源和发送源,建立在TCP之上的封装,传输层协议。是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口(不是协议,为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口)
- WebSocket:本质是网络传输协议,位于应用层上
轮询与WebSocket对比
- Ajax轮询:ajax轮询模拟长连接就是每隔一段时间就向服务器发起ajax请求,查询服务器端是否有数据更新
- 长轮询:客户端发送请求消息,当服务器有消息时,服务器返回值给客户端,客户端收到消息后,客户端再次发送连接。形如一直打电话,没收到就不挂电话
- WebSocket: 建立一次连接,当有新消息时,从服务器端向客户端推送消息
为什么WebSocket会解决服务器上消耗资源的问题
我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler来处理。简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(Handler)。本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢。,导致客服不够。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。这样就可以解决客服处理速度过慢的问题了。
Websocket使用dva中的subscriptions不能在同一路由下连接多个,否则先连接的会被自动断开
网友评论