最近尝试了一下服务器端的推送,以前的作法都是客户端轮询,定时向服务器发送请求。但这形成了个人一些困扰:php
1:轮询是由客户端发起的,那么在服务端就不能判别我要推送的内容是否已通过期,由于我很难判断某个信息是否已经推送给所有的客户端,那么服务端就须要缓存大量的数据。若是数据保存在数据库,那么还要每次请求都须要查询数据库,这对数据库和系统设计都是一个很大的挑战。html
2:请求的频率过高,每次的请求包中含有一样的数据,这对pc来讲也许算不得什么,可是对于移动客户端来说,这应该不是最佳的方案。尤为是遇到还要作权限判断的时候,那么服务端的逻辑和效率也会形成用户体验的下降。html5
好在Html5为咱们提供了一种方式:Server-Sent Events包含新的HTML元素EventSource和新的MIME类型 text/event-stream来完成个人须要。web
由于是第一次接触Html5,w3school中也有对EventSource的说明和使用。因而立刻开始着手实践。数据库
页面脚本就不用说了,按照w3school的方式便可。缓存
var source=new EventSource("demo_sse.php"); source.onmessage=function(event) { document.getElementById("result").innerHTML+=event.data + "<br />"; };
服务端的代码也是如初一折,w3school提供了php和asp的代码:服务器
//php方式 <?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $time = date('r'); echo "data: The server time is: {$time}\n\n"; flush(); ?> //asp方式 <% Response.ContentType="text/event-stream" Response.Expires=-1 Response.Write("data: " & now()) Response.Flush() %>
//代码解释:mvc
也许你们应该注意到,php和asp的案例有一点不同,就是php推送的信息一个使用了"\n\n"做为结束标志,而asp却没有。而本人实践则是用asp.net+mvc,通过测试,若是不以"\n\n"做为结束标志,那么客户端将不能接收到推送的值。还有须要特别声明一下:推送的信息格式必须为”data:内容\n\n“,不然。。。asp.net
public void Subscribe() { HttpContext.Response.ContentType = "text/event-stream"; HttpContext.Response.CacheControl = "no-cache"; HttpContext.Response.Write("data:" + DateTime.Now.ToString()+ "\n\n"); HttpContext.Response.Flush(); }
至此,客户端应该能够收到服务端推送的值。而如此简单的结构真的能够完成咱们须要的功能设计吗?
此例咱们只是推送了一个当前时间,而咱们实际要推送的值是不断变化的,否则也就没有推送的必要了。测试
因而我想到了将订阅的请求保存起来,当须要推送的时候,在对每一个请求进行循环推送,因而有了下面的代码:
public class PublishService { private static IDictionary<string, HttpResponseBase> contexts = new Dictionary<string, HttpResponseBase>(); public static void AddHttpContext(HttpContextBase context) { var token = context.GetToken(”CookieName“); if (!contexts.Keys.Contains(token)) contexts.Add(token, context.Response); } private static void Publish() { foreach (var context in contexts.Values) { context.ContentType = "text/event-stream"; context.CacheControl = "no-cache"; msg = GetData(context.GetToken("CookieName")); context.Write("data:" + msg + "\n\n"); context.Flush(); } }
public void Subscribe()
{
PublishService.AddHttpContext(HttpContext);
PublishService.Publish();
} }
但是在进行测试的时候Chrome告诉我:EventSource's response has a MIME type ("text/plain") that is not "text/event-stream". Aborting the connection.
而FF告诉我:Firefox 没法创建到 http://localhost:8000/Location/Notification/Subscribe 服务器的链接。
通过调试发现,在每次flush的时候发生异常:Server cannot flush a completed response.这到底是为啥呢?不管是google,仍是baidu,我都没能找到合适的答案,因此此案至今未结,如哪位知道请细说一二。
因而乎,我放弃了这种方式,转而就推送一个时间看看是什么效果。结果发现Chrome每隔3秒向客户端推送一次,而FF是每5秒推送一次。有了这样一个发现,那么服务端的设计就应该是另外一个样子:
public void Subscribe() { var data = GetData(); HttpContext.Response.ContentType = "text/event-stream"; HttpContext.Response.CacheControl = "no-cache"; HttpContext.Response.Write("data:" + data + "\n\n"); HttpContext.Response.Flush(); }
服务端只须要提供一个服务GetData(),这个服务用来获取咱们须要推送的信息,而根据Server-Sent Events规范推荐若是没有其余的数据要发送,那么按期的发送keep-alive注释。其余的事情就不用咱们操心了。
这只是一个简单的使用,由于本人在使用EventSource的时候走了一些弯路,因此写出来,但愿能对你们有些帮助。
总结:走了一些弯路的缘由是起初对Server-Sent Events机制的不清楚。我的理解:该机制依然并不是由服务器端发起,而是还由客户端向服务端定时发送请求,EventSource只是提供了一个很好的封装,不用本身去维护而已。
后记:
求教:EventSource.onopen和EventSource.onerror每次都会触发这两个事件,并且每次获得的结果都同样,为什么?