22.websocket初识
# 传统轮询(Traditional Polling)
当前Web应用中较常见的一种持续通信方式,通常采取setInterval或者setTimeout实现。例如如果我们想要定时获取并刷新页面上的数据,可以结合Ajax写出如下实现:
setInterval(function() {
$.get("/path/to/server", function(data, status) {
console.log(data);
});
}, 10000);
2
3
4
5
上面的程序会每隔10秒向服务器请求一次数据,并在数据到达后存储。这个实现方法通常可以满足简单的需求,然而同时也存在着很大的缺陷:在网络情况不稳定的情况下,服务器从接收请求、发送请求到客户端接收请求的总时间有可能超过10秒,而请求是以10秒间隔发送的,这样会导致接收的数据到达先后顺序与发送顺序不一致。于是出现了采用setTimeout的轮询方式:
function poll() {
setTimeout(function() {
$.get("/path/to/server", function(data, status) {
console.log(data);
// 发起下一次请求
poll();
});
}, 10000);
}
2
3
4
5
6
7
8
9
程序首先设置10秒后发起请求,当数据返回后再隔10秒发起第二次请求,以此类推。这样的话虽然无法保证两次请求之间的时间间隔为固定值,但是可以保证到达数据的顺序。
缺点 程序在每次请求时都会新建一个HTTP请求,然而并不是每次都能返回所需的新数据。当同时发起的请求达到一定数目时,会对服务器造成较大负担。
# 总结:
http协议的特点是服务器不能主动联系客户端,只能由客户端发起。它的被动性预示了在完成双向通信时需要不停的连接或连接一直打开,这就需要服务器快速的处理速度或高并发的能力,是非常消耗资源的。
# 什么是websocket
websocket是HTML5的一个新协议,它允许服务端向客户端传递信息,实现浏览器和客户端双工通信
# 背景
因为 HTTP 协议有一个缺陷:通信只能由客户端发起。 举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,轮询的效率低,非常浪费资源(因为必须不停连接,或者HTTP连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。
# websocket的特点
服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
- 与HTTP协议有着良好的兼容性。默认端口也是 80 和 443 ,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。
- 建立在TCP协议基础之上,和http协议同属于应用层
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信
- 协议标识符是ws(如果加密,则为wss),服务器网址就是URL,如ws://localhost:8023
参考:B站原生socket https://www.bilibili.com/video/BV1jy4y1U7UE?p=12&vd_source=d873e8e6b14a533f1b71cd27e69f7f7f
# WebSocket 通讯与传统 HTTP 的不同
在客户端,new WebSocket 实例化一个新的 WebSocket 客户端对象,连接类似 ws://yourdomain:port/path 的服务端 WebSocket URL,WebSocket 客户端对象会自动解析并识别为 WebSocket 请求,从而连接服务端端口,执行双方握手过程,客户端发送数据格式类似
- WebSocket 客户端连接报文
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin:
http://localhost
:8080
Sec-WebSocket-Version: 13
http://localhost
:8080
Sec-WebSocket-Version: 13
2
3
4
5
6
7
8
9
10
11
12
- ”Connection: Upgrade“ 表示升级链接
- ”Upgrade:websocket”参数值表明这是 WebSocket 类型请求,
- “Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的24随机字符序列密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。
- WebSocket 服务端响应报文
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
2
3
4
- “Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的
- “HTTP/1.1 101 Switching Protocols”中的101表示服务端接受 WebSocket 协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。
# 使用Websocket
- WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例
var ws = new WebSocket("ws://localhost:8080");
- ws.readyState
readyState 属性返回实例对象的当前状态,共有四种:
CONNECTING:值为 0,表示正在连接。
OPEN:值为 1,表示连接成功,可以通信了。
CLOSING:值为 2,表示连接正在关闭。
CLOSED:值为 3,表示连接已经关闭,或者打开连接失败。 - onopen属性:用于指定连接成功后的回调函数
- onclose属性:用于指定连接关闭后的回调函数
- onmessage属性:用于指定收到服务器数据后的回调函数
- send()方法:用于向服务器发送数据
- bufferedAmount属性:表示还有多少字节的二进制数据没有发送出去,它可以用来判断发送是否结束
- onerror属性:用于指定报错时的回调函数
var ws = new WebSocket(“ws://echo.websocket.org”);
ws.onopen = function(){ws.send(“Test!”); };
ws.onmessage = function(evt){console.log(evt.data);ws.close();};
ws.onclose = function(evt){console.log(“WebSocketClosed!”);};
ws.onerror = function(evt){console.log(“WebSocketError!”);};
2
3
4
5
当 Browser 和 WebSocketServer 连接成功后,会触发 onopen 消息;如果连接失败,发送、接收数据失败或者处理数据出现错误,browser 会触发 onerror 消息;当 Browser 接收到 WebSocketServer 发送过来的数据时,就会触发 onmessage 消息,参数 evt 中包含 Server 传输过来的数据;当 Browser 接收到 WebSocketServer 端发送的关闭连接请求时,就会触发 onclose 消息。我们可以看出所有的操作都是采用异步回调的方式触发,这样不会阻塞 UI,可以获得更快的响应时间,更好的用户体验。