乐趣区

关于websocket:websocket

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 字段生成步骤:

    1. 将 Sec-WebSocket-Key 与协定中已定义的一个 GUID“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”进行拼接。
    2. 将步骤 1 中生成的字符串进行 SHA1 编码。
    3. 将步骤 2 中生成的字符串进行 Base64 编码。

    客户端通过验证服务端返回的 Sec-WebSocket-Accept 的值, 来确定两件事件:

    1. 服务端是否了解 WebSocket 协定, 如果服务端不了解, 那么它就不会返回正确的 Sec-WebSocket-Accept,则建设 WebSocket 连贯失败。
    2. 服务端返回的 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 协定

退出移动版