分类:
2010-09-05 20:34:05
最近闲在家中,又突然想起这件事情,虽然说自我感觉这并不是正统的PUSH技术,但是毕竟已经有人在用,而且效果还不错,不如自己也来试试看。经过小小尝试,发现其实HTTP PUSH其实是一个很简单的原理,但效果确实不错,不过也有点需要解决的问题。
废话不说了,前面唐生了一段,入正题。
首先阐述一下HTTP PUSH的原理,众所周知,HTTP是无状态的,浏览器在REUQEST之后,WEB服务器做出处理,然后将处理结果RESPONSE到浏览器,完成后断开连接。在请求之前和完成之后,WEB服务器和浏览器之间没有任何关系,互不相干。因此我们在做聊天室等需要不断从服务器获取新数据的应用的时候,都是采用不断的去请求服务器,当然,AJAX的出现让用户体验直线上升不过仍然是不断去请求,只是用户不知道而已。这样看来,WEB服务器怎么PUSH啊?天方夜谭!每次请求都是独立的,无状态的,不过,如果说数据不断的更新其实就是一次请求呢?通常情况下,从REQUEST 到RESPONSE,WEB服务器只是干一系列的事后发出一次结果,时间上是比较短的,同时,REQUEST到RESPONSE这个过程中浏览器和服务器是有链接的。是的,这就是突破口,利用一个非正常的手段,延长从REQUEST到RESPONSE的时间,我们就可以获得这段时间的链接状态,让服务器在这段时间里都做些事情并立刻将进行中的结果发送过来而非等待全部完成后发送。
打个比方,一个人去邮局拿信,该邮局不会主动送信到家,每小时一次,到邮局后,他询问工作人员有没有他的信,无论有没有他都会回家,然后下i个小时再去问,周而复始,这就是通常的WEB聊天用的方法,浏览器定时去REQUEST,然后获得RESPONSE结束。现在我们换个方法,这个人不是每个小时去一次,而是打了个地铺长期睡在邮局里,他告诉邮局工作人员一有信就交给他,一旦有信,他就马上叫邮递员把信放到他家邮箱,这时候他坐在家里的老婆就会感觉到邮局现在是主动把信发到他家里了。这下明白了吧,就这么回事。
下面是我用ASP.NET实现的一个小例子,比较简单。
首先要建立两个页面,一个是用来更新信息,更新服务器上一个公共区域的信息。令一个页面就将用到PUSH方法,接收信息,当检查到服务器上公共区域的信息发生变化,服务器就立刻做出反应并把信息发送到页面上。本例中,ASPX页面不需要任何的客户端脚本,所以就不贴前台页面的代码了。
下面是代码:
发送端:
protected void Page_Load(object sender, EventArgs e)
{
Application["msg"] += "new date:" + DateTime.Now.ToString()+"
";
Response.Write(Application["msg"]);
}
为了能够方便的刷新页面来更新数据,可以加以个BUTTON按钮。
接收端:
protected void Page_Load(object sender, EventArgs e)
{
Response.Buffer = true;
if (Page.IsPostBack)
{
while (Response.IsClientConnected)
{
System.Threading.Thread.Sleep(1000);
if (Application["msg"] != Session["Current"])
{
Session["Current"] = Application["msg"];
Response.Write(Session["Current"].ToString());
Response.Flush();
}
else
{
Response.Write(".");
Response.Flush();
}
}
}
else
{
Session["Current"] = "";
Response.Write("Do on Request!
");
}
}
这里同样要在ASPX上加一个按钮,页面第一次加载后,点按钮进行第2次请求,第2次请求就将成为长请求。
下面进行一下代码说明,更新信息页没什么好说的,我用的是APPLICATION,方便点,当然也可以用数据库XML等之类的。
主要说下接手页。首先我们将该页面的Response.Buffer设置为ture,这样可以稳妥的控制好在我们不同意的情况下Response数据是保留在内存中的,当Page.IsPostBack时,说明是第2次请求了也就是要执行长请求了,用以个 while循环,条件是Response.IsClientConnect成立,也就是用户没有离开次页面,也没有停止请求的时候都一直循环,循环什么呢?循环检查当前用户的Session里的信息是否和Application全局变量里的信息相同,如果相同那么说明没有新的信息更新,立即输出一个“.”(为了测试方便设置的,其实可以什么都不做),当信息不同的时候,说明有更新了,因此就将Application里的东西复制给Session并将Session立即输出。OK 完成,我们可以测试一下,打开两个窗口,一个加载更新页,一个加载接收页,点击接收页的按钮让它开始执行长请求,这时候接收也应该是空当当的,然后我们点击更新页,页面新出现了我们执行请求的一行时间,再切换到接收页,YEAH,接收页立刻也出现了这个时间。这里要提个非常重要的东西,Response.Flush()方法,这个方法不是太经常用,但是很好用,作用是不需要等待请求完成立刻将内存中的结果输出到浏览器,这是PUSH相当重要的一个环节,因为我们本来就是要求请求是要长时间等待的,所以不可能让数据在请求完成后再发送,因此要利用FLUSH将数据立刻发送,这就是那个男人请的那个邮递员。另外还有一个重要的地方,System.Threading.Thread.Sleep(1000),这里我设置了没次循环让当前线程休息一秒,为什么呢?实际上用while的话,用户不离开这个请求就是完成不了的,一直等待着的,Thread.Sleep在这里并不是用来控制时间,而是保证CPU到使用率,去掉这句运行的话你会发现你的CPU永远100%,太频繁的检查更新非常消耗服务器资源,因此应该给个停顿时间, 邮局找信那家伙也是人别让人家累病了。
这样,一个长请求,也就是让WEB服务器PUSH信息的小例子就做好了,当然这个例子并不完善,不过用来了解工作原理还是很直观的。
不过,这个方法会阻塞线程,我们会发现,请求线程是一直被挂起的,因此同一个会话用户就无法再去请求这个服务器的其他资源了,因为同一个会话用户的请求线程服务器会让你等待之前的那个线程完成后再去执行,而那个线程被我们设置成了不会完成或者很长时间才能完成,这就好比,那个男人的老婆想让老公去帮他买晚餐的菜,可是他的老公却在邮局回不来。也许有人会想建立一个新的线程去做等待的事情或新的事情不就可以了吗?问题是,雇用保姆的工作也需要那个男人去做..........
目前为止本例没有解决关于阻塞线程的问题,我能想到的方法就是让被阻塞的线程与其他请求线程分开不在同一个会话里或不在同一个本请求的服务器上。这也是为什么在测试本例的时候要求先打开两个窗口。
好了,HTTP PUSH的原理和例子到目前我理解的就这些了,也许今后HTTPPUSH会有很好的改良或许现在就有,多学习多思考很重要。最后付上一个图例,更直观的理解HTTP PUSH
通常情况:浏览器REQUEST—>服务器处理—>服务器RESPONSE到浏览器
长请求PUSH:浏览器REQUEST—>服务器处理<===========>服务器RESPONSE到浏览器——————>最终