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