总结轮询、长轮询、Comet、SSE

一、comet 服务器推送

Ajax是一种从页面向服务器请求数据的技术,而comet是一种服务器向页面推送数据的技术。

短轮询/轮询

浏览器定时向服务器发送请求,看有没有更新的数据

短轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。不管服务端数据有没有变化,客户端都会发起请求,来获取数据。

  • 耗流量、有很多连接

浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源

1
2
3
4
5
6
7
8
9
10
11
12
// 客户端
var xhr = new XMLHttpRequest()
setInterval(() => {
xhr.open('GET', '/user')
xhr.onreadystatechange = () => { if (xhr.readyState == 4) { console.log(xhr.response);
}
}

xhr.send();
}, 1000)

// fetchsetInterval

两种实现Comet的方法

1. 长轮询

页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断。

客户端发送请求后,服务器端不会立即返回数据,服务器端会阻塞请求,连接不会立即断开,直到服务器端有数据更新或者是连接超时才返回,客户端才再次发出请求,新建连接,如此反复,从而获取最新数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//客户端
function getXHRNewData() {
let xhr = new XMLHttpRequest();
xhr.open('GET', '/user');
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
console.log(xhr.response);
getXHRNewData();
}
};
xhr.send();
}
getXHRNewData()


// getFetchNewData finally

无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两个最大的区别在于服务器如何/什么时候发送数据。短轮询是服务器立即发送响应,无论数据是否有效。长轮询是等待发送响应。轮询的优势是所有浏览器都支持,因为使用XHR对象和setTimeout就能实现

2. HTTP流

流在页面的整个生命周期内只使用一个HTTP连接。就是浏览器向服务器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。

  • P589

二、SSE 服务器发送事件(Server Sent Events)

围绕只读Comet交互推出的API或者模式。SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型是text/event-stream,而且浏览器中的JS API能够解析格式输出。SSE支持短轮询、长轮询、HTTP流,而且能在断开连接时自动确定合适重新连接。有了这个API,再实现Comet就容易多了。

依赖原生的HTTP,所以对于开发者来说更好理解。 比如在nodeJS,只要我不执行res.end(),并且一定时间持续发送信息的话,那么该连接就会持续打开(keep-alive)。其实通俗来说,就是一个长连接

1. SSE的API

SSE主要就是创建一个EventSource对象,并传进一个入口点,不过目前还不支持CORS,所以也被限制在同源策略下,所以传入的URL必须与创建对象页面同源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//指定路由发送
var source = new EventSource('/dates')

source.onopen = function(e) { //当连接正式建立时触发
console.log(e)
}

source.onmessage = function(e) { //监听信息的传输
var data = JSON.parse(e.data),
origin = e.origin
}
source.onerror = function(e) { //当连接发生error时触发
console.log(e)
}

  1. EventSource的实例有一个readyState属性

    1
    2
    3
    0:正连接到服务器
    1:打开了连接
    2:关闭了连接
  2. 实例还有3个事件

  • open:在建立连接时触发
  • message:在从服务器接收的新事件时触发
  • error:在无法建立连接时触发

    message事件的回调中event返回相关的数据,包括data-服务器返回的数据、origin-服务端URL的域名部分,服务器发回的数据以字符串的形式保存在event.data中。

  1. 默认情况下,EventSource对象会保持与服务器的活动连接。如果连接断开,还会重新连接。这就意味着SSE适合长轮询和HTTP流。如果想强制立即断开连接并且不再重新连接,可以调用close()
1
source.close()

2. 事件流

服务器返回数据格式:所谓的服务器事件会通过一个持久的HTTP响应发送,这个响应的类型为text/event-stream。响应的格式是纯文本,最简单的情况是每个数据项都带有前缀data:

1
2
3
4
5
6
data: foo

data: bar

data:foo
data:bar

对于以上响应,事件流中的第一个message事件返回event.data值为”foo”,第三个message事件返回event.data值为”foo\nbar”。对于多个连续的以data:开头的数据行,将作为多段数据解析,每个值之间以一个换行符分隔。只有在包含data:的数据行后面有空行时,才会触发message事件,因此在服务器上生成事件流时不能忘了多添加这一行。

1
2
data: foo
id: 1

通过id:前缀可以给特定的事件指定一个关联的ID,这个ID位于data:行前面或后面皆可。设置了ID后,EventSource对象会跟踪上一次触发的事件。如果连接断开,会先服务器发送一个包含名为Last-Event-ID的特殊HTTP头部的请求,以便服务器知道下一次该触发哪个事件。在多次连接的事件流中,这种机制可以确保浏览器以正确的顺序收到连接的数据段

1
2
3
4
5
6
7
8
9
10
11
12
data: hi

data: second event
id: 100

event: myevent
data: third event
id: 101

: this is a comment
data: fourth event
data: fourth event continue

每一段数据我们称之为事件,每一个事件经过空行分隔。“:”前面是数据类型,后面是数据。通常的类型有:

  • 空类型: 表示注释,在处理是会默认被删除.比如:this is a comment.
  • event: 声明该事件类型,比如message.
  • data: 最重要的一个类型, 表示传输的数据。可以为string格式或者JSON格式. 比如:data: {“username”: “bobby”}
  • id: 其实就是lastEventId. 用来表明该次事件在整个流中的序号
  • retry: 用来表明浏览器断开再次连接之前等待的事件(不常用)

服务器端不仅可以返回指定数据,还可以返回指定事件.不过默认情况下都是message事件, 但我们也可以指定事件,上面的myevent事件。即这就是触发自定义事件的方式。

1
2
3
4
var source = new EventSource('/someEvents');
source.addEventListener('myevent', function(event){
//doSth
}, false);

服务端使用SSE

由于使用的是HTTP协议,所以对于服务端基本上没什么太大的改变。唯一注意的就是发送数据使用res.write()即可,断开的时候使用res.end()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Access-Control-Allow-Origin": "*" //允许跨域
});
var num =0;
var f = function(){
if(num===10){
res.end();
}else{
res.write("id: " + num + "\n");
res.write("data: " + num + "\n\n");
num++;
}
setTimeout(f,1000);
}
f()

3. SSE和ajax的区别

  • 数据类型不同: SSE 只能接受 type/event-stream 类型,AJAX 可以接受任意类型;
  • 结束机制不同: 虽然使用AJAX长轮询也可以实现这样的效果,但是服务器端(以nodeJS为例)必须在一定时间内执行res.end()才行。而SSE只需要执行res.write() 即可