項目做完有一段時間了,一直想寫個博客總結一下,之前也沒寫過有質量的博客.一是怕寫出來被各位大牛笑話,二也是因為怕自己只了解了一點皮毛就發出來誤導了別人,所以一直沒怎么寫過博客,但是看很多大牛都鼓勵程序員寫博客,一來可以回顧一下自己做的項目中的重點,二也可以發現很多自己以前沒發現的問題.所以自己也試試寫一下吧,一直沒有總結的習慣,也想改改.文筆不好,經驗欠缺,各位輕噴.
-----------------------------------------------------分割線-----------------------------------------------
因為項目的需要,主管要求我做一個登錄后即時提醒的功能,即數據有變化的時候立即通知用戶.然后我就開始百度,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也不怕啦!
----------------------------------------分割線-------------------------------------
第一次發自認為是技術貼的帖子,如果大家覺得我哪里理解有誤請及時指出來,避免誤導他人.