您的位置:首页 > 理论基础 > 计算机网络

利用jquery Ajax和.Net IHttpAsyncHandler实现网站的即时提示

2017-07-17 13:09 302 查看
项目做完有一段时间了,一直想写个博客总结一下,之前也没写过有质量的博客.一是怕写出来被各位大牛笑话,二也是因为怕自己只了解了一点皮毛就发出来误导了别人,所以一直没怎么写过博客,但是看很多大牛都鼓励程序员写博客,一来可以回顾一下自己做的项目中的重点,二也可以发现很多自己以前没发现的问题.所以自己也试试写一下吧,一直没有总结的习惯,也想改改.文笔不好,经验欠缺,各位轻喷.

-----------------------------------------------------分割线-----------------------------------------------

因为项目的需要,主管要求我做一个登录后即时提醒的功能,即数据有变化的时候立即通知用户.然后我就开始百度,Google各种关键字搜索.最后知道有几种方式可以实现这种需求.即轮询和长连接.另外还有微软提供的一个开源的框架signalr(目前楼主本人就知道这些).

因为HTTP的无状态性,无连接性.导致web程序和服务器之间的数据传输只能是:浏览器向服务器发送一个请求,服务器再响应请求,然后返回要请求的数据.即浏览器和服务器的关系是请求--响应的关系,这种关系的好处就不说了(我也知道的不多 - -!),但是服务器却不能主动向浏览器发送数据,因为它是无状态的.那如果有这种需求了怎么办呢?聪明的人有很多,聪明人想出来解决的办法也挺多.前人栽树后人乘凉,咱们就先开始试试哪种方案最适合项目需求的.

1.signalr

园子里的已经有过介绍signalr的文章:SignalR 项目介绍 是张善友老师写的

我是通过在 Asp.NET MVC 中使用 SignalR 实现推送功能这篇文章了解到具体的使用方法,没有深入点的研究,它适用于做web即时聊天方面的.

楼主的项目则是要实现类似监视数据库的功能,所以不考虑这个方法,有兴趣的朋友可以去了解一下.

2.轮询

所谓轮询就是客户端不停的向服务器发送异步的请求,当发现数据库有变化时再通知浏览器做处理.这种方法实现起来简单,但是想想也知道,由于是不停的向服务器发送请求,对服务器来说是压力山大,要是同时打开的网页太多了话,有可能造成服务器崩溃.

3.长连接

前两种方法都不是LZ想要的,看来LZ就只能祭出那一招了:长连接.

楼主是百度GOOGLE党,就摘一段网友的话来解释长连接:客户端向服务器发送一个请求,服务器接收请求并hlod住这个连接,直到有数据或请求超时才返回客户端,客户端紧接着再发送一次请求,如此循环直到页面关闭,这也解释了为什么它叫长连接.比如这张图:


这张图的前两个请求超时我都设置为1分钟,返回后再立即发送一个请求.

好了,既然只剩下最后一招了,那就的把最后一招耍好,

首先是客户端要发送一个异步的请求:

/*客户端发出的异步请求*/
function asyncRequest() {
$.ajax({
type: "POST",
url: "asyncResult.asyn",
data: "time=60",    //请求的超时时间
success: function (data) {
if (data != "") {
/*执行操作,比如弹出提示*/
}
asyncRequest(); //得到服务器响应后继续发一个请求
},
error: function () {
asyncRequest(); //服务器抛出错误后继续发送一个请求
}
});
}


服务器接收这个异步请求的方法也要实现异步操作,要不然会阻塞正常的请求,所以要实现IHttpAsyncHandler这个接口,实现服务器的异步计算.

public class asyncResponse : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
myAsyncResult result = new myAsyncResult(context, cb, extraData);
asyncRequestMgr.add(result);
asyncRequestMgr.send();
return result;
}

public void EndProcessRequest(IAsyncResult result)
{
asyncRequestMgr.resultStr = "";     //异步结束时清空结果
}

public bool IsReusable
{
get { return false; }
}

public void ProcessRequest(HttpContext context)
{
}
}


asyncResponse类用来接收所有的异步请求,并交给静态类asyncRequestMgr来根据请求计算结果:

public static class asyncRequestMgr
{
public static string resultStr = "";
private static myAsyncResult asyncResult;
/// <summary>
/// 把一个异步的请求对象保存到静态对象中供操作
/// </summary>
/// <param name="result"></param>
public static void add(myAsyncResult result)
{
asyncResult = result;
}
/// <summary>
///
/// </summary>
public static void send()
{
string time = asyncResult.contex.Request.Form["time"];
getResult(time);
asyncResult.send(resultStr);    //发送数据到客户端
}
/// <summary>
/// 得到结果或返回空值
/// </summary>
private static void getResult(string time)
{
int i = int.Parse(time), temp = 0;
while (temp < i)
{
Thread.Sleep(1000);     //这个类继承自IHttpAsyncHandler,是由线程池中取出一个线程来执行本类,所以这里让线程Sleep(1000)不会影响到UI线程
/*
*这里再查询数据库,得到数据后保存至变量resultStr,再break出循环,
*/
  temp++;
       }
}
}


然后由myAsyncResult类来发送结果:

public class myAsyncResult : IAsyncResult
{
public HttpContext contex;
public AsyncCallback cb;
public object extraData;
/// <summary>
/// 初始化数据
/// </summary>
/// <param name="contex"></param>
/// <param name="cb"></param>
/// <param name="extraData"></param>
public myAsyncResult(HttpContext contex, AsyncCallback cb, object extraData)
{
this.contex = contex;
this.cb = cb;
this.extraData = extraData;
}
/// <summary>
/// 返回客户端请求的数据
/// </summary>
public void send(string resultStr)
{
this.contex.Response.Write(resultStr);
}
}


这样一个异步请求就算完成了,也实现了监视数据库的目的,但是如果客户不小心在后台查询数据库的时候按了刷新怎么办呢?这样建立起来的连接就会断开,而且由于我的前台是页面加载的时候开始异步请求,那一刷新一下又会再发送一次请求,而后台第一次的查询还在继续.这样后台就会有两次请求一起执行,一起查询数据库.再如果数据库的变化被第一次的请求查询到,但是第一次的请求因为客户刷新页面,连接已经断开,那用户也就不能得到数据变化的通知了.再再如果用户不小心无(手)意(贱)一直按着F5不放,那前台就会一直刷新一直请求,后台的N个请求同时查数据库.再再再如果有10个用户同时按F5不放,那就是10*N个请求同时查数据库,最后服务器只能不堪重负崩溃掉,如果这样怎么办呢?由于LZ平时MSDN看的少,确实苦恼了一阵子,最后突然发现HttpContext.Response有个属性:IsClientConnected,这个属性帮了大忙了,它返回一个BOOL值,表示当前请求是否在连接状态。有了这个属性就好办了,在getResult方法中加上判断,如果IsClientConnected==false的话,立即抛出一个异常,再把查询的结果保存到resultStr变量中,这样线程就不会继续执行下去.

修改后的getResult方法:

/// <summary>
/// 得到结果或返回空值
/// </summary>
private static void getResult(string time)
{
int i = int.Parse(time), temp = 0;
try
{
          while (temp < i)
{
  if (!asyncResult.contex.Response.IsClientConnected)
  throw new Exception();

Thread.Sleep(1000);     //这个类继承自IHttpAsyncHandler,是由线程池中取出一个线程来执行本类,所以这里让线程Sleep(1000)不会影响到UI线程
/*
*这里再查询数据库,得到数据后保存至变量resultStr,再break出循环,
*/
            temp++;
}
}
catch (Exception)
{
/*这里把异常的线程中的结果保存至resultStr中*/
throw;
}
}


然后在send方法执行前判断resultStr是不是空的,如果不是空的就不用查询数据库,直接发送resultStr:

/// <summary>
///
/// </summary>
public static void send()
{
if (resultStr == "")
{
string time = asyncResult.contex.Request.Form["time"];
getResult(time);
}
asyncResult.send(resultStr);    //发送数据到客户端
}


这样无论按多久的F5,只要服务器判断哪个请求的连接状态为false就抛出异常,保持最多只让一个请求来查询数据库,现在就算再怎么无()意()按F5也不怕啦!

----------------------------------------分割线-------------------------------------

第一次发自认为是技术贴的帖子,如果大家觉得我哪里理解有误请及时指出来,避免误导他人.

原文:
http://www.cnblogs.com/fhqqkqpnuii/archive/2013/06/07/3124270.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: