一、comet 服务器推送
Ajax是一种从页面向服务器请求数据的技术,而comet是一种服务器向页面推送数据的技术。
短轮询/轮询
浏览器定时向服务器发送请求,看有没有更新的数据
短轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。不管服务端数据有没有变化,客户端都会发起请求,来获取数据。
- 耗流量、有很多连接
浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源
1 | // 客户端 |
两种实现Comet的方法
1. 长轮询
页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断。
客户端发送请求后,服务器端不会立即返回数据,服务器端会阻塞请求,连接不会立即断开,直到服务器端有数据更新或者是连接超时才返回,客户端才再次发出请求,新建连接,如此反复,从而获取最新数据。
1 | //客户端 |
无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两个最大的区别在于服务器如何/什么时候发送数据。短轮询是服务器立即发送响应,无论数据是否有效。长轮询是等待发送响应。轮询的优势是所有浏览器都支持,因为使用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)
}
EventSource的实例有一个readyState属性
1
2
30:正连接到服务器
1:打开了连接
2:关闭了连接实例还有3个事件
- open:在建立连接时触发
- message:在从服务器接收的新事件时触发
- error:在无法建立连接时触发
message事件的回调中event返回相关的数据,包括data-服务器返回的数据、origin-服务端URL的域名部分,服务器发回的数据以字符串的形式保存在event.data中。
- 默认情况下,EventSource对象会保持与服务器的活动连接。如果连接断开,还会重新连接。这就意味着SSE适合长轮询和HTTP流。如果想强制立即断开连接并且不再重新连接,可以调用close()
1 | source.close() |
2. 事件流
服务器返回数据格式:所谓的服务器事件会通过一个持久的HTTP响应发送,这个响应的类型为text/event-stream。响应的格式是纯文本,最简单的情况是每个数据项都带有前缀data:
1 | data: foo |
对于以上响应,事件流中的第一个message事件返回event.data值为”foo”,第三个message事件返回event.data值为”foo\nbar”。对于多个连续的以data:开头的数据行,将作为多段数据解析,每个值之间以一个换行符分隔。只有在包含data:的数据行后面有空行时,才会触发message事件,因此在服务器上生成事件流时不能忘了多添加这一行。1
2data: foo
id: 1
通过id:前缀可以给特定的事件指定一个关联的ID,这个ID位于data:行前面或后面皆可。设置了ID后,EventSource对象会跟踪上一次触发的事件。如果连接断开,会先服务器发送一个包含名为Last-Event-ID的特殊HTTP头部的请求,以便服务器知道下一次该触发哪个事件。在多次连接的事件流中,这种机制可以确保浏览器以正确的顺序收到连接的数据段
1 | data: hi |
每一段数据我们称之为事件,每一个事件经过空行分隔。“:”前面是数据类型,后面是数据。通常的类型有:
- 空类型: 表示注释,在处理是会默认被删除.比如:this is a comment.
- event: 声明该事件类型,比如message.
- data: 最重要的一个类型, 表示传输的数据。可以为string格式或者JSON格式. 比如:data: {“username”: “bobby”}
- id: 其实就是lastEventId. 用来表明该次事件在整个流中的序号
- retry: 用来表明浏览器断开再次连接之前等待的事件(不常用)
服务器端不仅可以返回指定数据,还可以返回指定事件.不过默认情况下都是message事件, 但我们也可以指定事件,上面的myevent事件。即这就是触发自定义事件的方式。
1 | var source = new EventSource('/someEvents'); |
服务端使用SSE
由于使用的是HTTP协议,所以对于服务端基本上没什么太大的改变。唯一注意的就是发送数据使用res.write()即可,断开的时候使用res.end()
1 | res.writeHead(200, { |
3. SSE和ajax的区别
- 数据类型不同: SSE 只能接受 type/event-stream 类型,AJAX 可以接受任意类型;
- 结束机制不同: 虽然使用AJAX长轮询也可以实现这样的效果,但是服务器端(以nodeJS为例)必须在一定时间内执行res.end()才行。而SSE只需要执行res.write() 即可