chenqunfeng / Blog

个人技术记录博客
6 stars 1 forks source link

轮询、长轮询、长连接、SSE(Server-Sent Events)、WebSocket #11

Open chenqunfeng opened 7 years ago

chenqunfeng commented 7 years ago

轮询

浏览器发起一个Ajax请求,服务器收到后无论有无新数据,都会立即响应。一次响应结束后,连接断开,在setTimeout时间以后发起另一次Ajax请求。这就是所谓的轮询。

长轮询

长轮询与轮询的不同之处在与,当没有新数据时,服务器会挂起这个请求,并时不时去查看是否有新数据,直到有新数据时才响应,或者连接超时后自动断开。然后与轮询相同的是,长轮询在得到响应后也会在setTimeout时间以后发起另一次长轮询请求。与轮询相比,减少了无用的轮询,也就是减少了无用的HTTP频繁建立和断开的损耗。

长连接

长连接,指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。也就是说,利用Connection: keep-alive,在连接超时之前,只建立一次TCP连接实现后续同样的Ajax多次请求都通过这个连接传输,而不需要重复建立连接和断开连接。

HTTP1.0和HTTP1.1的支持情况

在HTTP1.0中,是不支持持久连接的,每个连接都是一次性的,所以HTTP1.0中并不支持长连接;而在HTTP1.1中,增加了持久连接,减少了重复建立连接的损耗。 PS:在现在几乎所有的网址中,几乎所有的请求都会默认带上Connection: keep-alive,这是因为现在的框架都会默认将keep-alive开启,虽然会导致原本是短连接的请求都变成了长连接,但是在server内存足够的情况下,一般影响都不大。所以,也因为这个原因,现在我们使用的轮询基本都是长连接。

请求响应中不开启keep-alive

1507876309 1 1507876309 1 不开启keep-alive的情况下的同个Ajax请求,我们可以看到每一次都重新建立了连接。

请求响应中开启keep-alive

1507876395 1 1507876487 1 开启keep-alive的情况下的同个Ajax请求,我们可以看到,除了第一次请求以外,在超时时间内的后续请求,都无需重复建立请求,而是都通过第一次建立的连接进行传输。

SSE(Server-Sent Events)

HTTP协议属于拉取协议,所以正常情况下服务器无法主动向客户端推送消息。不过,有一种变通的方法,就是服务器向客户端推送流消息(stream),像视频那样,在视频下载完毕之前,源源不断往客户端推送流消息,客户端也不会关闭连接。 SSE就是利用这种机制,让服务器向客户端推送流消息。而SSE也是基于HTTP协议,目前除了IE/Edge不支持之外其他都支持,默认支持断线重连,一般都是用来传送文本,二进制数据需要编码后传送。

SSE图示

1507888001 1 1507888045 1

SSE具体实现

客户端

var source = new EventSource(url);   
source.onopen = function (event) {
  console.log('Connection open ...');
};
source.onerror = function (event) {
  console.log('Connection close.');
};
source.addEventListener('chatStream', function (event) {
  console.log('firstChat: ' + event.data);
});
source.onmessage = function (event) {
  console.log('message: ' + event.data);
};

服务端(node)

// 纯node实现
var http = require("http");
http.createServer(function (req, res) {
  var fileName = req.url;
  if (fileName === "/chat") {
    res.writeHead(200, {
      "Content-Type":"text/event-stream",
      "Cache-Control":"no-cache",
      "Connection":"keep-alive",
      "Access-Control-Allow-Origin": '*',
    });
    res.write("event: chatStream\n");
    res.write("data: Hi \n\n");
    interval = setInterval(function () {
      res.write("data: Hello \n\n");
    }, 1000);
    req.connection.addListener("close", function () {
      clearInterval(interval);
      res.end();
    }, false);
  }
}).listen(3000);
// express实现且使用了compression中间件
router.get('/chat', function(req, res, next) {
    res.set({
        "Content-Type":"text/event-stream",
        "Cache-Control":"no-cache",
        "Connection":"keep-alive",
        "Access-Control-Allow-Origin": '*',
    });
    res.write("event: chatStream\n");
    res.write('data: Hi \n\n');
    res.flush();
    interval = setInterval(function () {
        res.write('data: Hello \n\n');
        res.flush();
    }, 2000);
    req.connection.addListener("close", function () {
        clearInterval(interval);
        res.end();
    }, false);
})

说明

WebSocket

WebSocket是HTML5新出的协议,是一种基于TCP之上的客户端与服务器全双工通讯的协议,不属于HTTP协议,也和HTTP没有关系。它属于持久化协议,这一点与非持久化的HTTP协议有很大不同。

握手阶段

帧协议

f3717ce3fa01c640c7e406d8ff7477d