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

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

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

因为项目的需要,主管要求我做一个登录后即时提醒的功能,即数据有变化的时候立即通知用户.然后我就开始百度,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也不怕啦!

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

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

posted @ 2013-06-07 17:22  龙卷风摧毁停车场!  阅读(6627)  评论(8编辑  收藏  举报