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 协定