初识WebSocket
HTTP 协议特点
- 单向请求
- 无状态
- 半双工
通信只能由客户端主动发起请求来获取服务端数据
WebSocket是什么
WebSocket是一种可以创建和服务器间进行 双向会话 的高级技术,通过WebSocket可以向服务器发送消息并接受基于事件驱动的响应。这样就不用向服务器轮询获取数据。
双向会话:客户端和服务端都能够通过WebSocket来进行数据相互传递,即服务端可以给客户端推送数据,客户端也可以通过WebSocket向服务端传递数据。
为什么要使用WebSocket?
在不使用WebSocket时,我们需要建立长连接时。建立长连接的几种主要方法:
- 轮询
- 长轮询
- SSE(Server Send Event)
轮询
轮询是最早在客户端用来模拟长连接的一种方式。他通过客户端定时向服务端发送HTTP请求来模拟客户端向服务端发送数据,而服务端的数据则是在客户端发送HTTP请求后跟随返回
缺点: 服务端的数据需要在客户端的请求回来后才能带回。如果HTTP请求的间隔太短,则会导致大量的网络开销;如果间隔太长,这将导致数据传递的不及时。
长轮询
长轮询是在轮询的基础上改进的一种方式。在客户端发送HTTP请求且服务端收到请求时,服务端会先维持这个请求不返回。在特定的时间内(一般为30秒,因为通常HTTP判断超时时间为30秒),如果服务端没有数据,则回应这个请求;服务端有数据需要发送时,则立即通过HTTP请求的响应将数据传递给客户端。客户端收到响应后,立即发起下一次的HTTP请求。
优缺点:能够解决轮询中带来的服务端数据不能及时传递的问题,但是带来的网络花销大的问题仍然无法解决。
SSE(Server Send Event)
SSE一种新的协议,用于服务端向客户端推送数据,通过SSE实现数据的单向推送功能。
缺点: 单向推送,只能从服务端向客户端推送数据、
WebSocket能够解决的问题
WebSocket能够有效的解决上述问题,还包括以下问题:
- 带宽问题:WebSocket相对HTTP协议头更小,按需传递数据
- 数据实时性问题:WebSocket相对轮询和长轮询来说能够实时传递数据,延迟更少
- 状态问题:相对无状态的HTTP,WebSocket在建立连接后会维持特定的状态
WebSocket协议
WebSocket协议是由HTTP协议升级而来的,只要在HTTP协议的基础上增加两次握手(如果是需要通过SSL加密,则还需要进行SSL握手过程),即可建立WebSocket连接。以下Header相关字段
请求Header
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
其中:
- Host: server.example.com:表示将要连接的WebSocket地址。
- Connection: Upgrade:表示HTTP连接需要升级
- Upgrade: websocket:表示将HTTP连接升级为WebSocket连接
- Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==:客户端生成的WebSocket密钥
- Sec-WebSocket-Protocol: chat, superchat:指定客户端那些WebSocket协议是可以接受的
- Sec-WebSocket-Version: 13:指定WebSocket版本
响应Header
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
其中:
- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo:服务端根据客户端生成的连接密钥生成服务端密钥
- Connection: Upgrade:确认升级HTTP连接
- Upgrade: websocket:确认将HTTP连接升级为WebSocket连接
- Sec-WebSocket-Protocol: chat:服务端选择的WebSocket协议
WebSocket API
WebSocket的API比较简单,按使用顺序包括:
- 建立连接
- 接收消息
- 发送消息
- 关闭连接
建立连接
WebSocket 通过实例化来创建连接,通过实例的open事件的回调来确认连接创建成功,如:
const websocket = new WebSocket('ws://server.example.com');
websocket.addEventListener('open', e => {
// 建立连接成功...
});
ps:在WebSocket建立ws连接时,url可以是域名或者IP地址;但是当建立的连接是wss(加密WebSocket)时,url必须是域名,因为需要配置相应的证书,而证书是针对域名的。
接收消息
WebSocket 通过message事件来监听消息接收,如:
websocket .addEventListener('message', e => {
// 消息接收成功
});
WebSocket可以传递String、ArrayBuffer和Blob三种数据类型,因此在收到消息时可能是其中的任意一种。其中,String和ArrayBuffer使用的最多。
发送消息
WebSocket 的发送消息是通过实例的send方法来实现的。
websocket.send(data);
关闭连接
被动关闭
当服务端被动关闭WebSocket连接时,会通过WebSocket向客户端发送一个close数据包,会触发实例的WebSocket close事件如:
websocket.addEventListener('close', e => {
// WebSocket连接关闭
});
注:当网络断开时,WebSocket连接并不会被动关闭,因为没有收到关闭的数据包。
主动关闭
客户端可以通过WebSocket提供的close方法来主动关闭长连接
websocket.close();
注:主动关闭不会触发close事件。
其他客户端API
websocket.readyState
readyState
属性返回实例对象的当前状态,共有四种
- CONNECTING 值为0 表示正在连接
- OPEN 值为1 表示已经连接,可以通信
- CLOSING 值为2 表示连接正在关闭
- CLOSED 值为3 表示连接已经关闭
websocket.bufferedAmount
实例对象的bufferedAmount
属性,表示还有多少字节的二进制数据没有发送出去,可以通过该属性来判断数据发送是否结束。
var data = new ArrayBuffer(10000000);
socket.send(data);
if (socket.bufferedAmount === 0) {
// 发送完毕
} else {
// 发送还没结束
}
websocket.binaryType
binaryType
属性,显式指定收到的二进制数据类型
// 收到的是 blob 数据
websocket.binaryType = "blob";
websocket.onmessage = function(e) {
console.log(e.data.size);
};
// 收到的是 ArrayBuffer 数据
websocket.binaryType = "arraybuffer";
websocket.onmessage = function(e) {
console.log(e.data.byteLength);
};
总结
通过WebSocket的长连接,客户端和服务端可以进行大量的数据传输而不会带来相关的性能问题,这给Web端带来了极大的功能增强。目前Web端可以使用WebSocket来进行IM相关功能开发,或者实时协作等需要与服务端进行大量数据交互的功能,并且不需要像之前一样使用长轮询的Hack方式来实现。