websocket在什么背景下诞生?
短轮询(Polling)
短轮询(Polling)的实现思路就是浏览器端每隔几秒钟向服务器端发送http申请,服务端在收到申请后,不管是否有数据更新,都间接进行响应。在服务端响应实现,就会敞开这个Tcp连贯,如下图所示:
示例代码实现如下:
function Polling() { fetch(url).then(data => { // somthing }).catch(err => { console.log(err); });}setInterval(polling, 5000);
- 长处:能够看到实现非常简单,它的兼容性也比拟好的只有反对http协定就能够用这种形式实现。
- 毛病:然而它的毛病也很显著就是十分的耗费资源,因为建设
Tcp
连贯是十分耗费资源的,服务端响应实现就会敞开这个Tcp
连贯,下一次申请再次建设Tcp
连贯。
长轮询(Long-Polling)
客户端发送申请后服务器端不会立刻返回数据,服务器端会阻塞申请连贯不会立刻断开,直到服务器端有数据更新或者是连贯超时才返回,客户端才再次发出请求新建连贯、如此重复从而获取最新数据。大抵成果如下:
客户端的代码如下:
function LongPolling() { fetch(url).then(data => { LongPolling(); }).catch(err => { LongPolling(); console.log(err); });}LongPolling();
- 长处: 长轮询和短轮询比起来,显著缩小了很多不必要的http申请次数,相比之下节约了资源。
- 毛病:连贯挂起也会导致资源的节约。
WebSocket
WebSocket是一种协定,是一种与HTTP 等同的网络协议,两者都是应用层协定,都基于 TCP 协定。然而 WebSocket 是一种双向通信协定,在建设连贯之后,WebSocket 的 server 与 client 都能被动向对方发送或接收数据。同时,WebSocket在建设连贯时须要借助 HTTP 协定,连贯建设好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
相比于短轮询、长轮询的每次“申请-应答”都要client 与 server 建设连贯的模式,WebSocket 是一种长连贯的模式。就是一旦WebSocket 连贯建设后,除非client 或者 server 中有一端被动断开连接,否则每次数据传输之前都不须要HTTP 那样申请数据。
另外,短轮询、长轮询服务端都是被动的响应,属于单工通信。而websocket客户端、服务端都能被动的向对方发送音讯,属于全双工通信。
WebSocket 对象提供了一组 API,用于创立和治理 WebSocket 连贯,以及通过连贯发送和接收数据。浏览器提供的WebSocket API很简洁,调用示例如下:
var ws = new WebSocket('wss://example.com/socket'); // 创立平安WebSocket 连贯(wss)ws.onerror = function (error) { ... } // 错误处理ws.onclose = function () { ... } // 敞开时调用ws.onopen = function () { ws.send("Connection established. Hello server!");} // 连贯建设时调用向服务端发送音讯ws.onmessage = function(msg) { ... }// 接管服务端发送的音讯复制代码
HTTP、WebSocket 等应用层协定,都是基于 TCP 协定来传输数据的。咱们能够把这些高级协定了解成对 TCP 的封装。既然大家都应用 TCP 协定,那么大家的连贯和断开,都要遵循 TCP 协定中的三次握手和四次握手 ,只是在连贯之后发送的内容不同,或者是断开的工夫不同。对于 WebSocket 来说,它必须依赖 HTTP 协定进行一次握手 ,握手胜利后,数据就间接从 TCP 通道传输,与 HTTP 无关了。
websocket是怎么握手的?
- 浏览器、服务器建设TCP连贯,三次握手。这是通信的根底,传输管制层,若失败后续都不执行。
- TCP连贯胜利后,浏览器通过HTTP协定向服务器发送带有Upgrade头的HTTP Request音讯
Connection:HTTP1.1中规定Upgrade只能利用在间接连贯中。带有Upgrade头的HTTP1.1音讯必须含有Connection头,因为Connection头的意义就是,任何接管到此音讯的人(往往是代理服务器)都要在转发此音讯之前解决掉Connection中指定的域(即不转发Upgrade域)。
Upgrade是HTTP1.1中用于定义转换协定的header域。 如果服务器反对的话,客户端心愿应用曾经建设好的HTTP(TCP)连贯,切换到WebSocket协定。
Sec-WebSocket-Key是一个Base64encode的值,这个是客户端随机生成的,用于服务端的验证,服务器会应用此字段组装成另一个key值放在握手返回信息里发送客户端。
Sec-WebSocket-Version标识了客户端反对的WebSocket协定的版本列表。
Sec-WebSocket-Extensions是客户端用来与服务端协商扩大协定的字段,permessage-deflate示意协商是否应用传输数据压缩,client_max_window_bits示意采纳LZ77压缩算法时,滑动窗口相干的SIZE大小。
Sec_WebSocket-Protocol是一个用户定义的字符串,用来辨别同URL下,不同的服务所须要的协定,标识了客户端反对的子协定的列表。 服务器收到客户端的握手申请后,同样采纳HTTP协定回馈。
HTTP的版本为HTTP1.1,返回码是101,示意降级到websocket协定
Connection字段,蕴含Upgrade
Upgrade字段,蕴含websocket
Sec-WebSocket-Accept字段,具体介绍一下:Sec-WebSocket-Accept字段生成步骤:
- 将Sec-WebSocket-Key与协定中已定义的一个GUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”进行拼接。
- 将步骤1中生成的字符串进行SHA1编码。
- 将步骤2中生成的字符串进行Base64编码。
客户端通过验证服务端返回的Sec-WebSocket-Accept的值, 来确定两件事件:
- 服务端是否了解WebSocket协定, 如果服务端不了解,那么它就不会返回正确的Sec-WebSocket-Accept,则建设WebSocket连贯失败。
- 服务端返回的Response是对于客户端的此次申请的,而不是之前的缓存。 次要是避免有些缓存服务器返回缓存的Response.
- 至此,握手过程就实现了,此时的TCP连贯不会开释。客户端和服务端能够相互通信了。
websocket如何身份认证?
大体上Websocket的身份认证都是产生在握手阶段,通过申请中的内容来认证。一个常见的例子是在url中附带参数。
new WebSocket("ws://localhost:3000?token=xxxxxxxxxxxxxxxxxxxx");
淘宝的直播弹幕也是用这种形式做的身份认证。另外,websocket是采纳http协定握手的,能够用申请中携带cookie的形式做身份认证。
以npm的ws模块实现为例,其创立Websocket服务器时提供了verifyClient办法。
const wss = new WebSocket.Server({ host: SystemConfig.WEBSOCKET_server_host, port: SystemConfig.WEBSOCKET_server_port, // 验证token辨认身份 verifyClient: (info) => { const token = url.parse(info.req.url, true).query.token let user console.log('[verifyClient] start validate') // 如果token过期会爆TokenExpiredError if (token) { try { user = jwt.verify(token, publicKey) console.log(`[verifyClient] user ${user.name} logined`) } catch (e) { console.log('[verifyClient] token expired') return false } } // verify token and parse user object if (user) { info.req.user = user return true } else { info.req.user = { name: `游客${parseInt(Math.random() * 1000000)}`, mail: '' } return true } }})
相干的ws源码位于ws/websocket-server
// ... if (this.options.verifyClient) { const info = { origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], secure: !!(req.connection.authorized || req.connection.encrypted), req }; if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortHandshake(socket, code || 401, message); this.completeUpgrade(extensions, req, socket, head, cb); }); return; } if (!this.options.verifyClient(info)) return abortHandshake(socket, 401); } this.completeUpgrade(extensions, req, socket, head, cb);}
websocket如何断开重连?
websocket如何解决高并发?
参考:深入浅出Websocket(一)Websocket协定