短轮询(Polling)的实现思路就是浏览器端每隔几秒钟向服务器端发送http请求,服务端在收到请求后,不管是否有数据更新,都直接进行响应。在服务端响应完成,就会关闭这个Tcp链接,以下图所示:git
示例代码实现以下:github
function Polling() { fetch(url).then(data => { // somthing }).catch(err => { console.log(err); }); } setInterval(polling, 5000);
Tcp
链接是很是消耗资源的,服务端响应完成就会关闭这个Tcp
链接,下一次请求再次创建Tcp
链接。客户端发送请求后服务器端不会当即返回数据,服务器端会阻塞请求链接不会当即断开,直到服务器端有数据更新或者是链接超时才返回,客户端才再次发出请求新建链接、如此反复从而获取最新数据。大体效果以下:web
客户端的代码以下:算法
function LongPolling() { fetch(url).then(data => { LongPolling(); }).catch(err => { LongPolling(); console.log(err); }); } LongPolling();
WebSocket是一种协议,是一种与HTTP 同等的网络协议,二者都是应用层协议,都基于 TCP 协议。可是 WebSocket 是一种双向通讯协议,在创建链接以后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket在创建链接时须要借助 HTTP 协议,链接创建好了以后 client 与 server 之间的双向通讯就与 HTTP 无关了。npm
相比于短轮询、长轮询的每次“请求-应答”都要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 无关了。服务器
服务器收到客户端的握手请求后,一样采用HTTP协议回馈。
HTTP的版本为HTTP1.1,返回码是101,表示升级到websocket协议
Connection字段,包含Upgrade
Upgrade字段,包含websocket
Sec-WebSocket-Accept字段,详细介绍一下:websocket
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.
大致上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); }