利用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也不怕啦!

----------------------------------------分割線-------------------------------------

第一次發自認爲是技術貼的帖子,如果大家覺得我哪裏理解有誤請及時指出來,避免誤導他人.

原文:

http://www.cnblogs.com/fhqqkqpnuii/archive/2013/06/07/3124270.html

發佈了5 篇原創文章 · 獲贊 8 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章