`

HTML5中的服务器‘推送’技术 -Server-Sent Events

阅读更多
HTML5中的服务器‘推送’技术 -Server-Sent Events
转帖:http://www.developersky.net/thread-63-1-1.html

一直以来,HTTP协议都是严格遵循Request-Response模型的。客户端发送一个Request到服务器,服务器对Request作出响应并将Response发送回客户端。也就是说,所有的互动都是由客户端发起的,服务器不会发起任何互动。
为了创建互动性更强的web应用程序,AJAX出现了,AJAX实现了一个动态的从Server获取数据的方法。通过使用AJAX,浏览器通过XMLHttpRequest API来发送HTTP request。XMLHttpRequest使得我们可以在不阻塞用户界面的情况下向服务器发送异步的HTTP request来获取数据。但是AJAX并没有定义新的HTTP request类型,只是将发送HTTP request的工作移到了后台,不影响用户的操作。因此AJAX也没有打破Request-Response的模型,还是由浏览器从服务器‘拉’数据。
另外一种技术是Comet,也称为反向Ajax。和Ajax一样,Comet也是建立在已经存在的HTTP协议之上的。Comet会维护一个长期存活的HTTP连接,发送‘假’的请求从而得到response。
这些都是为了打破HTTP协议的限制的解决方法。但是在HTML5中,这种限制会被打破。HTML5规范中包含很多功能强大的特性,能够将浏览器变成功能齐全的RIA客户端平台。Server-Sent Event和WebSockets就是其中的两个特性,这两个特性能够帮助我们实现服务器将数据‘推送’到客户端的功能。
在这篇文章中我们先介绍一下Server-Sent Events特性
Server-Sent Events
Server-Sent Events实际上将Comet技术进行了标准化。Server-Sent Events规范“定义了API来打开一个HTTP连接,通过该连接能够获取从服务器推送的通知”。Server-Sent Events包含新的HTML元素EventSource和新的MIME类型 text/event-stream,这个MIME类型定义了事件框架格式。

view plaincopy to clipboardprint?
01.<html> 
02.   <head> 
03.     <mce:script type='text/javascript'><!--  
04.        var source = new EventSource('Events');  
05.        source.onmessage = function (event) {  
06.           ev = document.getElementById('events');  
07.           ev.innerHTML += "<br>[in] " + event.data;  
08.        };  
09.       
10.// --></mce:script> 
11.  </head> 
12.  <body> 
13.    <div id="events"></div> 
14.  </body> 
15.</html> 
<html>
   <head>
     <mce:script type='text/javascript'><!--
        var source = new EventSource('Events');
        source.onmessage = function (event) {
           ev = document.getElementById('events');
           ev.innerHTML += "<br>[in] " + event.data;
        };
    
// --></mce:script>
  </head>
  <body>
    <div id="events"></div>
  </body>
</html>

EventSource代表的是接收事件的客户端的终点。客户端通过创建EventSource对象来打开一个event stream。创建EventSource对象时,该对象接收一个事件来源的URL作为其构造函数的参数。当每次收到新的事件数据时onmessage事件处理器会被调用。
通常情况下,浏览器会限制到每个服务器的连接的数量。在有些情况下,装载多个包含到同一个域的EventSource对象的页面会导致对每个EventSource创建一个专属于该EventSource的连接,这种情况下很快就会超出连接数量限制。为了处理这种情况,我们可以使用共享的WebWorker,该对象共享一个EventSource的实例。另外,通过定义浏览器特定的EventSource实现,我们可以做到如果两个EventSource的URL是相同的,那么他们就重用相同的连接。这时,共享的连接就由浏览器特定的EventSource实现来管理。
当event stream打开的时候,浏览器会发送如下的HTTP request。
REQUEST:

view plaincopy to clipboardprint?
01.GET /Events HTTP/1.1  
02.Host: myServer:8875  
03.User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE)   
04.        AppleWebKit/532+ (KHTML, like Gecko) Version/4.0.4   
05.        Safari/531.21.10  
06.Accept-Encoding: gzip, deflate  
07.Referer: http://myServer:8875/  
08.Accept: text/event-stream  
09.Last-Event-Id: 6  
10.Accept-Language: de-DE  
11.Cache-Control: no-cache  
12.Connection: keep-alive    
GET /Events HTTP/1.1
Host: myServer:8875
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE)
        AppleWebKit/532+ (KHTML, like Gecko) Version/4.0.4
        Safari/531.21.10
Accept-Encoding: gzip, deflate
Referer: http://myServer:8875/
Accept: text/event-stream
Last-Event-Id: 6
Accept-Language: de-DE
Cache-Control: no-cache
Connection: keep-alive   

Accept定义了需要的格式 text/event-stream。 虽然Server-Sent Events规范定义了text/event-stream的MIME 类型,该规范同时允许使用其他的事件框架格式。但是Server-Sent Events的实现必须支持test/event-stream格式。
根据text/event-stream的格式,一个事件有一个或多个注释行和字段行组成。注释行是由冒号:开始的行。字段域行由字段名和字段值组成,字段名和字段值也是由冒号:分隔。多个事件之间用空行分隔。下面就是一个Response的例子:
RESPONSE:

view plaincopy to clipboardprint?
01.HTTP/1.1 200 OK  
02.Server: xLightweb/2.12-HTML5Preview6  
03.Content-Type: text/event-stream  
04.Expires: Fri, 01 Jan 1990 00:00:00 GMT  
05.Cache-Control: no-cache, no-store, max-age=0, must-revalidate  
06.Pragma: no-cache  
07.Connection: close  
08. 
09.: time stream  
10.retry: 5000  
11. 
12.id: 7  
13.data: Thu Mar 11 07:31:30 CET 2010  
14. 
15.id: 8  
16.data: Thu Mar 11 07:31:35 CET 2010  
17. 
18.[...] 
HTTP/1.1 200 OK
Server: xLightweb/2.12-HTML5Preview6
Content-Type: text/event-stream
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Connection: close

: time stream
retry: 5000

id: 7
data: Thu Mar 11 07:31:30 CET 2010

id: 8
data: Thu Mar 11 07:31:35 CET 2010

[...]

根据定义,Event stream不应该被缓存。为了避免缓存,在Response的头中包含了Cache-Control,禁止了缓存该response。
上面的例子中,该response中包含三个事件。第一个事件包含一个注释行和一个retry字段;第二个事件和第三个事件都是包含一个id字段和一个data字段。data字段中包含的是事件的数据,在上面的例子中是当前的时间。id字段是用来在event stream中跟踪处理进程的。上面的例子中,服务器端的应用程序会每隔5秒向event stream中写入一个事件。当EventSource接收到该事件后,onmessage事件处理器就会被调用。
不同的是,第一个事件不会触发onmessage处理器。第一个个事件没有data字段,只包含一个注释行和一个retry字段,retry字段是用于重新连接的目的的。retry字段定义了重新连接的时间,单位是毫秒。如果收到了这样的字段,EventSource会更新其相关的重新连接时间的属性。在发生网络错误的情况下,重新连接时间在提高可靠性方面扮演了重要的角色。当EventSource实例发现连接断开了,在指定的重新连接时间之后会自动的重建连接。
我们可以看到,在HTTP request中,我们可以指定Last-Event-Id。EventSource在重建连接的时候会指定该值。每次EventSource收到包含id字段的事件时,EventSource的last event id属性会被更改,在重建连接的时候,EventSource的last event id属性会被写入HTTP request的Last-Event-Id中。这样如果服务器端实现了lastEventId的处理,就可以保证在重建的连接中不会发送已经收到的事件了。
下面的代码是一个基于Java HTTP 库xLightweb(包含HTML5预览扩展)的HttpServer的例子。

view plaincopy to clipboardprint?
01.class ServerHandler implements IHttpRequestHandler {  
02.  private final Timer timer = new Timer(false);  
03.  public void onRequest(IHttpExchange exchange) throws IOException {  
04.    String requestURI = exchange.getRequest().getRequestURI();  
05.    if (requestURI.equals("/ServerSentEventExample")) {  
06.      sendServerSendPage(exchange, requestURI);  
07.    } else if (requestURI.equals("/Events")) {  
08.      sendEventStream(exchange);  
09.    } else {  
10.      exchange.sendError(404);  
11.    }  
12.  }  
13.  private void sendServerSendPage(IHttpExchange exchange,   
14.          String uri) throws IOException {  
15.    String page = "<html>\r\n " +  
16.        " <head>\r\n" +  
17.        "     <mce:script type='text/javascript'><!--  
18.\r\n" +  
19.        "        var source = new EventSource('Events');\r\n" +  
20.        "        source.onmessage = function (event) {\r\n" +  
21.        "          ev = document.getElementById('events');\r\n" +  
22.        "          ev.innerHTML += \"<br>[in] \" + event.data;\r\n"+  
23.        "        };\r\n" +  
24.        "       
25.// --></mce:script>\r\n" +  
26.        " </head>\r\n" +  
27.        " <body>\r\n" +  
28.        "    <div id=\"events\"></div>\r\n" +  
29.        " </body>\r\n" +  
30.        "</html>\r\n ";  
31.    exchange.send(new HttpResponse(200, "text/html", page));  
32.  }  
33.  private void sendEventStream(final IHttpExchange exchange)   
34.          throws IOException {  
35.    // get the last id string  
36.    final String idString = exchange.getRequest().getHeader(  
37.            "Last-Event-Id", "0");  
38.    // sending the response header  
39.    final BodyDataSink sink = exchange.send(new   
40.            HttpResponseHeader(200, "text/event-stream"));  
41.    TimerTask tt = new TimerTask() {  
42.       private int id = Integer.parseInt(idString);  
43.       public void run() {  
44.         try {  
45.           Event event = new Event(new Date().toString(), ++id);  
46.           sink.write(event.toString());  
47.         } catch (IOException ioe) {  
48.           cancel();  
49.           sink.destroy();  
50.         }  
51.       };  
52.    };  
53.    Event event = new Event();  
54.    event.setRetryMillis(5 * 1000);  
55.    event.setComment("time stream");  
56.    sink.write(event.toString());  
57.    timer.schedule(tt, 3000, 3000);  
58.  }  
59.}  
60.XHttpServer server = new XHttpServer(8875, new ServerHandler());  
61.server.start(); 
class ServerHandler implements IHttpRequestHandler {
  private final Timer timer = new Timer(false);
  public void onRequest(IHttpExchange exchange) throws IOException {
    String requestURI = exchange.getRequest().getRequestURI();
    if (requestURI.equals("/ServerSentEventExample")) {
      sendServerSendPage(exchange, requestURI);
    } else if (requestURI.equals("/Events")) {
      sendEventStream(exchange);
    } else {
      exchange.sendError(404);
    }
  }
  private void sendServerSendPage(IHttpExchange exchange,
          String uri) throws IOException {
    String page = "<html>\r\n " +
        " <head>\r\n" +
        "     <mce:script type='text/javascript'><!--
\r\n" +
        "        var source = new EventSource('Events');\r\n" +
        "        source.onmessage = function (event) {\r\n" +
        "          ev = document.getElementById('events');\r\n" +
        "          ev.innerHTML += \"<br>[in] \" + event.data;\r\n"+
        "        };\r\n" +
        "    
// --></mce:script>\r\n" +
        " </head>\r\n" +
        " <body>\r\n" +
        "    <div id=\"events\"></div>\r\n" +
        " </body>\r\n" +
        "</html>\r\n ";
    exchange.send(new HttpResponse(200, "text/html", page));
  }
  private void sendEventStream(final IHttpExchange exchange)
          throws IOException {
    // get the last id string
    final String idString = exchange.getRequest().getHeader(
            "Last-Event-Id", "0");
    // sending the response header
    final BodyDataSink sink = exchange.send(new
            HttpResponseHeader(200, "text/event-stream"));
    TimerTask tt = new TimerTask() {
       private int id = Integer.parseInt(idString);
       public void run() {
         try {
           Event event = new Event(new Date().toString(), ++id);
           sink.write(event.toString());
         } catch (IOException ioe) {
           cancel();
           sink.destroy();
         }
       };
    };
    Event event = new Event();
    event.setRetryMillis(5 * 1000);
    event.setComment("time stream");
    sink.write(event.toString());
    timer.schedule(tt, 3000, 3000);
  }
}
XHttpServer server = new XHttpServer(8875, new ServerHandler());
server.start();

Server-Sent Events规范推荐如果没有其他的数据要发送,那么定期的发送keep-alive注释。这样代理服务器就可以在某个HTTP连接有一段时间不活跃时关闭该连接,这样代理服务器能够关闭空闲的连接来避免浪费连接在没有响应的HTTP服务器上。发送注释事件使得这种机制不会发生在有效的连接上。尽管EventSource会自动重建连接,但是发送注释事件还是能够避免不必要的重新连接。
Server-Sent Event是基于HTTP streaming的。如上所述,response会一直打开,当服务器端有事件发生的时候,事件会被写入response中。理论上来说,如果网络的中介如HTTP代理不立即转发部分的response,HTTP streaming会导致一些问题。现在的HTTP RFC (RFC2616 Hypertext Transfer Protocal – HTTP/1.1)没有要求部分的response必须被立刻转发。但是,很多已经存在的流行的、工作良好的web应用程序是基于HTTP streaming的。而且,产品级别的中介通常会避免缓冲大量的数据来降低内存的占用率。
和其他的流行的Coment协议如Bayeux和BOSH不同,Server-Sent Event只支持单向的从服务器到客户端的通道。Bayeux协议支持双向的通信通道。另外,Bayeux能够使用HTTP Streaming和轮询。BOSH协议也支持双向通信通道,但是BOSH是基于轮询机制的。(所谓的轮询就是客户端定期发送request到服务器端来获取数据)。
尽管Server-Sent Events比Bayeux和BOSH的功能要少,但是在只需要单向的服务器向客户端推送数据的情况下(在很多情况下都是这样),Server-Sent Events有潜力成为占主导地位的协议。Server-Sent Events协议被Bayeus和BOSH要简单的多。另外,Server-Sent Events被所有兼容HTML5的浏览器支持(这就是规范的威力啊)。

转帖:http://www.developersky.net/thread-63-1-1.html
分享到:
评论
4 楼 marshan 2013-10-28  
服务器可以异步执行
3 楼 flex_莫冲 2013-10-14  
marshan 写道
这个间隔可以由服务器端完成 无伤大雅

服务器端完成无非就是用sleep实现延迟,这是一种假循环间隔。这种方式不好。服务器得保持连接资源直到sleep后再返回数据释放连接资源。对服务器的负担更重了。
2 楼 marshan 2013-10-13  
这个间隔可以由服务器端完成 无伤大雅
1 楼 flex_莫冲 2013-09-26  
SSE就是循环执行ajax。SSE还不能自定义循环时间间隔。

相关推荐

    基于JPA开发的Server-Sent-Event数据推送

    这样,即使在页面加载后,浏览器也能接收到服务器推送的新数据。 在压缩包中的文件“ServerSent”,可能包含了实现SSE功能的核心代码,比如Controller的实现,Repository的接口,以及可能的实体类定义。这些文件...

    服务端消息推送Server-Sent示例代码

    在IT行业中,服务端消息推送(Server-Sent Events,简称SSE)是一种高效且实时的通信技术,允许服务器主动向客户端发送数据,而无需客户端频繁发起请求。这种单向推送机制在实时应用如股票更新、在线聊天、实时通知...

    浅谈HTML5 服务器推送事件(Server-sent Events)

    服务器推送事件(Server-sent Events)是基于WebSocket 协议的一种服务器向客户端发送事件&数据的单向通讯。目前所有主流浏览器均支持服务器发送事件,当然除了 Internet Explorer 。2333… WebSocket 协议是继HTTP...

    服务器推送技术

    源码在服务器推送技术的实现中扮演着关键角色,因为它涉及到网络协议的解析、数据处理和事件驱动编程。开发者通常会使用如Node.js、Java、Python等支持异步I/O的编程语言来编写服务器端代码,以实现高效的推送逻辑。...

    服务端推技术 - Server-side Push 多示例

    3. **EventSource/SSE(Server-Sent Events)**:这是一种单向的推送机制,服务器可以将事件以文本流的形式持续发送到客户端。与WebSocket相比,SSE更简单,但只能从服务器向客户端推送数据。 在"comet_broadcast.a...

    服务器推送示例-支持IE火狐谷歌等

    在IT行业中,服务器推送是一种技术,它允许服务器主动地向客户端发送数据,而不仅仅是响应客户端的请求。这种技术可以显著提高实时性,特别是在需要实时更新数据的应用场景中,如聊天应用、股票报价、在线游戏等。在...

    HTML5推送django-monitio.zip

    django-monitio 允许你有消息(通知)之后: 可以被保存(存储在数据库中,可以...django-monitio 使用了HTML5中的服务器'推送'技术--Server-Sent Events,可以实时地、动态地推送消息给用户。 标签:django

    .net 服务器推送

    在C# .NET环境中实现服务器推送,有多种方法,如WebSocket、SignalR、Server-Sent Events (SSE)等。下面将详细介绍这些技术: 1. WebSocket: WebSocket提供全双工通信,允许服务器和客户端双向传输数据。在C#中,...

    ServerPush(服务器推送)

    在Web开发中,服务器推送(Server Push)是一种技术,它允许服务器主动将数据发送到客户端,而无需等待客户端的请求。这种机制打破了传统的HTTP协议的请求-响应模型,提高了实时性和交互性。在ASP.NET框架下实现...

    .net实现Server Push(服务器推送)源码

    以下是对标题和描述中涉及的.NET实现Server Push(服务器推送)源码的详细解释: 首先,`ServerPush.sln`是一个Visual Studio解决方案文件,它包含了项目的所有配置和依赖关系。当你打开这个文件时,Visual Studio...

    逐句回答,流式返回,ChatGPT采用的Server-sent events后端实时推送协议Python3.10实现

    Server-sent events(SSE)是一种用于实现服务器到客户端的单向通信的协议。使用SSE,服务器可以向客户端推送实时数据,而无需客户端发出请求。 SSE建立在HTTP协议上,使用基于文本的数据格式(通常是JSON)进行通信...

    HTML5 服务器发送事件(Server-Sent Events)

    Server-Sent Events是HTML5引入的一个强大特性,它简化了实时数据推送的实现,减少了不必要的网络通信,提高了实时应用的效率。对于需要实时更新的Web应用,Server-Sent Events是一个理想的选择。

    html5服务器推送_动力节点Java学院整理

    为了解决这个问题,HTML5引入了服务器推送事件(Server-Sent Events,SSE)和WebSocket两种技术。 WebSocket是一种基于TCP协议的双向通信规范,它允许服务器和客户端之间持续保持连接,实现低延迟的数据交换。然而...

    server-sent-events:使用Spring WebFlux框架和React式Kafka的WebFlux服务器,该服务器公开了REST API,以使客户端发出安全的HTTP请求。 在客户端和Web Flow服务器之间建立安全连接后,除非有必要,否则它将使用来自Kafka主题的消息并异步推送数据,而无需关闭与客户端的连接

    一旦在客户端和Web Flow服务器之间建立了安全连接,除非有必要,否则它将使用来自Kafka主题的消息并异步推送数据,而无需关闭与客户端的连接。 使用Reactive Kafka看一下Spring WebFlux上的这个。

    基于服务器推送技术建立在线交流平台的探讨与实现

    服务器推送技术(Server Pushing)的核心在于改变信息流动的方向,由传统的“拉”式(Pull)变为“推”式(Push),即服务器主动向客户端推送信息。这一技术的应用不仅提高了信息传递的效率和稳定性,还能更好地支持...

    asp.net 服务器推技术源码

    3. Server-Sent Events (SSE):SSE允许服务器单向向客户端推送事件,但只支持服务器到客户端的通信,不支持双向交互。 4. 帧套帧技术(IFrame,AJAX):利用IFrame或者AJAX的定时刷新机制,服务器在接收到请求后...

Global site tag (gtag.js) - Google Analytics