項目做完有一段時間了,一直想寫個部落格總結一下,之前也沒寫過有品質的部落格.一是怕寫出來被各位大牛笑話,二也是因為怕自己只瞭解了一點皮毛就發出來誤導了別人,所以一直沒怎麼寫過部落格,但是看很多大牛都鼓勵程式員寫部落格,一來可以回顧一下自己做的項目中的重點,二也可以發現很多自己以前沒發現的問題.所以自己也試試寫一下吧,一直沒有總結的習慣,也想改改.文筆不好,經驗欠缺,各位輕噴.
-----------------------------------------------------分割線-----------------------------------------------
因為項目的需要,主管要求我做一個登入後即時提醒的功能,即資料有變化的時候立即通知使用者.然後我就開始百度,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, */ } } }
然後由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 { if (!asyncResult.contex.Response.IsClientConnected) throw new Exception(); while (temp < i) { Thread.Sleep(1000); //這個類繼承自IHttpAsyncHandler,是由線程池中取出一個線程來執行本類,所以這裡讓線程Sleep(1000)不會影響到UI線程 /* *這裡再查詢資料庫,得到資料後儲存至變數resultStr, */ } } 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也不怕啦!
----------------------------------------分割線-------------------------------------
第一次發自認為是技術貼的文章,如果大家覺得我哪裡理解有誤請及時指出來,避免誤導他人.