解讀asp.net中的觀察者模式(2)_.Net教程
推薦:解讀Asp.net教程:設計IP地址屏蔽功能出于安全考慮,幾乎每個動態網站都具備IP地址屏蔽功能,而網上流傳的很多關于該功能的教程大都采用字符串保存和驗證IP地址,我認為這是不太科學的,我試圖找到最佳的設計方案。 “I
第二個問題: 當管理員頁面的ajax請求的時候,每兩個請求如何保存數據?呵呵,上面那個問題不是說了么,用單件,但是單件是全局存在的,我們的管理員是多個,每個管理員可以決定是否訂閱數據,以及什么時候訂閱。想起來沒?除了全局數據外我們還有Session
在管理頁面上我放置一個“開始監視”的按鈕,這個按鈕使用ajax請求服務器端的一個HttpHandler,在Handler的ProcessRequest方法里這樣來做:
| 以下為引用的內容: [img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img] Admin admin = context.Session["monitor_listener"] as Admin; [img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img] if(admin == null) [img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif[/img] [img]http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif[/img] [img]http://www.cnblogs.com/Images/dot.gif[/img]{ [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] admin = new Admin(Monitor.Current); [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Session["monitor_listener"] = admin; [img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif[/img]} [img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img]
|
注意,由于這個Handler需要訪問Session,所以你需要讓這個Handler繼承IRequiresSessionState接口(為什么使用繼承而不用實現這個術語?實際上這個接口是一個標記接口,沒有任何需要實現的成員,只是標記這個Handler可以訪問Session,我不知道為什么MS不使用Attribute,是不是更合理些)
在管理頁面還有個一個SetInterval不斷的調用一個含有ajax的方法,去請求另外一個Handler,這個Handler將Admin收到的數據返回到web頁面,讓我們來看看這個Handler的部分實現:
| 以下為引用的內容: img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img] public void ProcessRequest(HttpContext context) [img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif[/img] [img]http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif[/img] [img]http://www.cnblogs.com/Images/dot.gif[/img]{ [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Response.Buffer = true; [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Response.ExpiresAbsolute = System.DateTime.Now.AddSeconds(-1); [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Response.Expires = 0; [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Response.CacheControl = "no-cache"; [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] Admin admin = context.Session["monitor_listener"] as Admin; [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] if (admin == null || admin.MessageCollection == null || admin.MessageCollection.Count <= 0) [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] return; [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] string[] messages = new string[admin.MessageCollection.Count]; [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] admin.MessageCollection.CopyTo(messages, 0); [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] StringBuilder sb = new StringBuilder(); [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] for (int i = 0; i < messages.Length; i ) [img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif[/img] [img]http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif[/img] [img]http://www.cnblogs.com/Images/dot.gif[/img]{ [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] sb.AppendFormat("<li>{0}</li>", messages); [img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif[/img] } [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] admin.MessageCollection.Clear(); [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Session["monitor_listener"] = admin; [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Response.Write(sb); [img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img] context.Response.Flush(); [img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif[/img]} [img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img] |
[OK,一個在asp.net環境中實現的觀察者模式基本上就算完成了,不過上面只有怎樣訂閱,那什么時候取消訂閱了,可以在Session_End事件里面取消訂閱
還查看了一些關于長連接的文章,發現這個不錯,準備改進一下。
完整的代碼稍后提供,希望這塊轉頭能引來一些玉
寫完這個Post后本來想把完整代碼實現傳上來,后來看到不少園友提出異議,看了大家的留言后我也一直在思索:我為什么這樣做?當初我是怎樣想到這個解決方案的?我在幾個解決方案之間做了取舍了么?我這樣做是不是矯枉過正了?經過這些思考有了現在的這個Post。
首先我進一步談一下需求:
這是一個Web Application,有很多客戶端向服務器端提交數據(客戶端是C 的,以http-post方式向服務器端提交二進制數據,服務器端解析這個二進制包,數據提交很頻繁),管理員可以進入監視頁面瀏覽這些數據,數據要即時的,客戶端發來一條,管理員屏幕上要馬上可以看到,允許多個管理員同時監視即時數據,所有管理員看到的數據都是一樣的(目前是這樣的,也許以后對管理員要分角色,各角色管理員看到的信息將不同)。
由于數據提交非常頻繁,客戶要求不允許頻繁的數據庫操作,所以我將數據保存在一個IList的緩存里面,當這個IList的大小超過了我在配置文件里定義的大小的時候就將數據批量插入到數據庫。
下面我將以我當初思考的思路為主線描述:
第一個版本:
| 以下為引用的內容: //在程序里我寫了一個靜態類,這個靜態類保存整個程序中共享的一些數據, 相當于原來的//Application對象,但是靜態成員是編譯期類型檢查的 public static ApplicationData { //這個隊列用來保存客戶端傳遞過來的數據,當隊列達到一定長度的時候同步到數據庫 public static Queue<DataHead> OperateDataList = new Queue<DataHead>(); //這個List也是保存客戶端傳遞過來的數據的,但它是為監視準備數據的, //當一個監視頁面的請求到來的時候將這個List的數據Response過去,然后Clear這個//List public static IList<DataHead> MonitorDataList = new List<DataHead>(); } public class ReciveDataHandler : IHttpHandler { //…… Public void ProcessRequest(HttpContext context) { //解析從客戶端傳遞過來的數據 DataHead data = GetData(context); OperateDataList.Add(data); If(OperateDataList.Count > BufferSize) { //將數據寫入到數據庫 AddToBase(); } MonitorDataList.Add(data); } } //監視頁面從這里獲取數據 public class MonitorHandler : IHttpHandler { //…… Public void ProcessRequest(HttpContext context) { If(MonitorDataList.Count > 0) { //將MonitorDataList里的數據Response出去 OutPut(); MonitorDataList.Clear(); } } } |
說實話,我當初做出這個的時候覺得一點問題都沒有,開始的時候客戶測試也沒有發現任何問題,終于有一天客戶和我同時測試部署在同一IIS的時候,問題出現了:只有一個監視頁面有數據。看到這個后我還百思不得其解,順著程序的執行流程一步一步走下去,沒有找出任何錯誤。后來做了下日志,原來MonitorDataList是一個全局共享的,一個在監視把數據Clear了后別人就無法獲取數據了。不知道有沒有人這樣做過:有時候忘記了自己正在做一個web程序,而web程序是一個并發的,對一些共享資源的訪問有著微妙的問題,如果沒有記住這點,按照程序流程的執行步驟是找不出任何問題的。
怎么辦?再一看這不是事件訂閱所描述的場景么?所以就有了上一篇Post的Solution。不過那個方案受到不少人質疑,其中金色海洋提出這樣的方法:
| 以下為引用的內容: Public class ReciveData : IHttpHandler { //………. //將客戶端傳遞過來的數據存入數據庫 }
Public class MonitorHandler : IHttpHandler |
看似這個方案不錯,我嘗試著將我的程序修改為這樣,但是我將上面的代碼編寫完,我發現我不可以再進行下去了:上面的方案滿足不了我的需求,客戶明確要求了客戶端提交的數據要先緩存然后緩存超過配置大小(這個大小還需要可以在配置文件里面配置,以便可以經過測試找出一個最合理的值),而這種Session記錄的方案是依靠數據庫來保存數據,這個Session[“id”]就相當于一個游標,這個游標指向的是數據庫,那好,我們將Session[“id”]指向緩存數據,但是請注意緩存隨時可能超過設置大小而被同步到數據庫并被清空。
分享:.Net教程之HTTP狀態碼200,301,302跳轉非常常用,在哪里都一樣,這里的一些說明和用法也如此,不止適用于asp.net,其他語言也會用得到。跳轉的目的本來很簡單,就是當用戶或系統需要時從一個頁面轉向另一個頁面,但自從有了
- asp.net如何得到GRIDVIEW中某行某列值的方法
- .net SMTP發送Email實例(可帶附件)
- js實現廣告漂浮效果的小例子
- asp.net Repeater 數據綁定的具體實現
- Asp.Net 無刷新文件上傳并顯示進度條的實現方法及思路
- Asp.net獲取客戶端IP常見代碼存在的偽造IP問題探討
- VS2010 水晶報表的使用方法
- ASP.NET中操作SQL數據庫(連接字符串的配置及獲取)
- asp.net頁面傳值測試實例代碼
- DataGridView - DataGridViewCheckBoxCell的使用介紹
- asp.net中javascript的引用(直接引入和間接引入)
- 三層+存儲過程實現分頁示例代碼
- 相關鏈接:
- 教程說明:
.Net教程-解讀asp.net中的觀察者模式(2)
。