实现Comet模型:基于 HTTP 长连接的“服务器推”技术
以笔者自己做过的一个实时监控项目举例:涉及Extjs+ashx异步轮询终端发送过来的消息
AJAX 的出现使得 JavaScript 可以调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新。使用 AJAX 实现“服务器推”与传统的 AJAX 应用不同之处在于:
- 服务器端会阻塞请求直到有数据传递或超时才返回。
- 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
- 当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。
一些应用及示例如 “Meebo”, “Pushlet Chat” 都采用了这种长轮询的方式。相对于“轮询”(poll),这种长轮询方式也可以称为“拉”(pull)。因为这种方案基于 AJAX,具有以下一些优点:请求异步发出;无须安装插件;IE、Mozilla FireFox 都支持 AJAX。
在这种长轮询方式下,客户端是在 XMLHttpRequest 的 readystate 为 4(即数据传输结束)时调用回调函数,进行信息处理。当 readystate 为 4 时,数据传输结束,连接已经关闭。Mozilla Firefox 提供了对 Streaming AJAX 的支持, 即 readystate 为 3 时(数据仍在传输中),客户端可以读取数据,从而无须关闭连接,就能读取处理服务器端返回的信息。IE 在 readystate 为 3 时,不能读取服务器返回的数据,目前 IE 不支持基于 Streaming AJAX。
function beginRequest()
{
Ext.Ajax.request(
{
url: GtsMap.Config.rootData + 'Svc/TraLisrSvc.ashx',
scriptTag: true,
method: 'post',
params: { AckId: _ackId },
timeout: 30000,
disableCaching: true,
scope: this,
success: function(resp, opts)
{
//增加对服务器返回状态的判断:登录信息
if (resp.status == 202)
{
Ext.Msg.show(
{
closable: false,
icon: Ext.Msg.WARNING,
buttons: Ext.Msg.OK,
title: GtsMap.Lang.msg_title_warning,
msg: GtsMap.Lang.login_expired,
fn: function() { this.app.exit(); },
scope: this
});
_work = false;
return;
}
if (resp.responseText.length > 0)
{
var evt = Ext.decode(resp.responseText);
if (evt)
{
// _ackId = evt.TraEventId;
this.recv(evt);
}
// if (_work && (resp.tId == (_tid + 1))) {
if (_work)
{
//重新请求,实现 Http Comet
// _tid = _tid + 1;
beginRequest.defer(_CYCLE_INT, this);
}
}
},
failure: function(resp, opts)
{
if (_work)
{
// _tid = resp.tId;
beginRequest.defer(_CYCLE_ERR, this);
}
//增加对错误的处理
//如果只是 超时,则重新开始监听
// if (resp.isTimeout) {
// if (_work) {
// _sendTime = new Date().toLocaleTimeString();
// beginRequest.defer(_CYCLE_ERR, this);
// }
// }
// else {
// Ext.Msg.show(
// {
// closable: false,
// icon: Ext.Msg.WARNING,
// buttons: Ext.Msg.OK,
// title: GtsMap.Lang.msg_title_error,
// msg: GtsMap.Lang.app_server_error,
// fn: function() { this.app.exit();},
// scope: this
// });
// }
}
});
};
使用异步 HTTP 处理程序,您可以在启动一个外部进程(如对远程服务器的方法调用)的同时继续执行该处理程序。该处理程序可以继续运行,而不必等待外部进程完成。
在异步 HTTP 处理程序的处理过程中,ASP.NET 将通常用于外部进程的线程放回线程池中,直到处理程序接收到来自外部进程的回调。由于只能同时执行有限数量的线程,因此这样可以避免阻止线程并改善性能。如果许多用户都在请求依赖于外部进程的同步 HTTP 处理程序,那么操作系统可能很快就会用完所有线程,因为大量线程被阻止,正在等待外部进程。
Asp.net环境下实现Comet就要用到 IHttpAsyncHandler接口
实现其中的BeginProcessRequest和EndProcessRequest方法
BeginProcessRequest方法中的HttpContext包含了Response和Request对象,AsyncCallback用于异步调用结束,运行结束后需要返回一个IAsyncResult对象。
using System;
using System.Web;
using System.Threading;
class HelloWorldAsyncHandler : IHttpAsyncHandler
{
public bool IsReusable { get { return false; } }
public HelloWorldAsyncHandler()
{
}
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
{
context.Response.Write("<p>Begin IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
AsynchOperation asynch = new AsynchOperation(cb, context, extraData);
asynch.StartAsyncWork();
return asynch;
}
public void EndProcessRequest(IAsyncResult result)
{
}
public void ProcessRequest(HttpContext context)
{
throw new InvalidOperationException();
}
}
class AsynchOperation : IAsyncResult
{
private bool _completed;
private Object _state;
private AsyncCallback _callback;
private HttpContext _context;
bool IAsyncResult.IsCompleted { get { return _completed; } }
WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
Object IAsyncResult.AsyncState { get { return _state; } }
bool IAsyncResult.CompletedSynchronously { get { return false; } }
public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
{
_callback = callback;
_context = context;
_state = state;
_completed = false;
}
public void StartAsyncWork()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncTask), null);
}
private void StartAsyncTask(Object workItemState)
{
_context.Response.Write("<p>Completion IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
_context.Response.Write("Hello World from Async Handler!");
_completed = true;
_callback(this);
}
}
参考资料:http://msdn.microsoft.com/zh-cn/library/ms227433(v=VS.90).aspx
http://www.ibm.com/developerworks/cn/web/wa-lo-comet/