請注意這個實時打上了雙引號,沒有絕對的實時,只是時間的顆粒不一樣罷了(1ms,1s,1m)。
服務器數據有更新可以快速通知客戶端。Web 基於取得模式,而服務器建立大量的和客戶端連接來提供數據實時更新反而拉低服務器的使用效能。
請下載DEMO 點擊下載
一、現有方案歸納有兩類。
-
服務器真實推送 - 基於瀏覽器外部控件數據實時更新。
IE ActiveX(flash)控件,還有其他瀏覽器比如Firefox插件。這種基於瀏覽器外部插件的,由於移植性差。主要要在一些瀏覽器安全上得到應用。比如在線支付(支付寶),自動登陸(QQ)。和一些內網控制(電網內部控制管理)。 Flash 其實也是瀏覽器的一種插件后台通過建立Socket 來與客戶端實時數據更新。這點比較有優勢的是Flash 插件幾乎每台機器上都有安裝,移植性沒有問題。但同樣對防火牆穿透能力差,而且需要消耗服務器大量的。
-
基於XMLHttpRequest 定時取的解決方案.
Ajax 通過定時去詢問服務器是否有數據更新,似乎是一個通用的解決方案,如:1元xx 類的網站,因為搶購模式需要實現更新商品的剩余份數。比如要獲取服務器當前參與人數,獲取最新購買人數,發送的私信,好友消息,每一種類型數據都設定一個時間如:1秒到數據庫取一次數據。而大多數請求的鏈接是無效的,而且過多的請求會導致瀏覽器無響應。
二、為什么我們不能兩者結合,選擇折中的方案呢?
客戶端腳本需要一個可以告知的程序,告訴我們服務器中有數據更新了,然后執行自己注冊好的程序到服務器取對應的數據。
我們只需要服務器通知提供這樣的數據結構:
{ //用戶消息更新時間 msg: '20141192003261234', //網站購買記錄更新時間 buy: '20141192003534567' }
獲取客戶端獲取當前的數據后 和自己當前瀏覽器中存儲的用戶消息更新時間進行對比。如果msg更新時間與服務器給的時間完全一致,我們就沒有必要到服務器中去用戶個人消息了,反之服務器中消息更新,開始執行我們預定的程序來獲取服務器,用戶收到的消息。顯示給用戶,並且設置一下當前消息的更新時間。
我們的需要的就這么簡單——需要一個通知程序通知我們
三、我們需要做什么,會遇到那些問題。
讓服務器通知客戶端程序數據更新顯然不是很划算的事情我們上面討論過了,所以我們需要在客戶端設置一個循環往復的定時程序到服務器里面去取注冊好的類型(用戶消息,網站成交數量,購買人次,商品剩余數量)的更新的時間,通過對於注冊對於數據類型的時間來判斷要不要執行我們注冊好的方法。
問題1:定時程序必須有序執行Ajax方法(前一個ajax完畢后才能發送第二次Ajax),服務器獲取數據是一個耗時操作。
Ajax異步遞歸。
問題2:並不是每一個頁面都需要知道數據的變動情況
不同的頁面注冊不同的監聽信息。
問題3:如何動態開始和阻止定時程序,並且保持程序只能運行一個定時實例。
設置互斥量.
問題4:服務器如何存儲這些數據的更新狀態(公共的:成交量,個體的:用戶消息)。
需要一個存儲介質,存儲對應類型信息的更新時間
問題5:如何在異常中恢復。確保定時程序能正常運行。
Try ... Catch... $.ajax error.
四、代碼實現
客戶端定時程序:
//客戶端監聽對象 var listener = { tid: 0, keys: "", //任務存儲對象 taskType: {}, //注冊一個任務 appendTaskType: function (key, type) { if (typeof (type) == "function" && typeof (key) == "string" && /^[a-z0-9]+$/.test(key)) { //添加一個任務 this.taskType[key] = { //任務執行函數 fun: type, //變化量 ts: '' }; var a = []; for (var k in this.taskType) { a.push(k); } this.keys = a.join('.'); } }, //開始運行監聽 start: function () { //如果定時器正在運行則返回 if (this.tid != 0) return; fn(); //私有定時執行方法 function fn() { $.getJSON("/api/listener", { keys: listener.keys }, function (d) { //獲取定時器所有的注冊類型 for (var key in listener.taskType) { //獲取注冊類型對象 var O = listener.taskType[key]; //判斷當前對象是否存在和當前的值是否和之前的變化值一樣,是否真正執行 if (d[key] && d[key] != O.ts) { //更改現有狀態 O.ts = d[key]; //執行注冊函數 O.fun(O); } } //設置ID listener.tid = setTimeout(fn, 1000); }) } }, //關閉監聽 stop: function () { //清除定時器 clearTimeout(this.tid); //歸零 this.tid = 0; } } //注冊對頁面的監聽 listener.appendTaskType("msg", function () { $.getJSON("/api/getlist", { key: 'msg' }, function (data) { var b = $("#msgList"); b.hide(); b.empty(); var html = ''; for (var i = 0; i < data.length; i++) { html += "<li>【" + data[i].user + "】說:" + data[i].msg + "</li>" } b.html(html); b.slideDown(300); }) }); //注冊對購買記錄的監聽 listener.appendTaskType("buy", function () { $.getJSON("/api/getlist", { key: 'buy' }, function (data) { var b = $("#buyList"); b.hide(); b.empty(); var html = ''; for (var i = 0; i < data.length; i++) { html += "<li>【" + data[i].user + "】購買了:" + data[i].msg + "</li>" } b.html(html); b.slideDown(300); }) }) //開始執行監聽任務 listener.start();
服務器代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web; using System.Web.Mvc; namespace MvcApplication1.Controllers { public class ApiController : Controller { // // GET: /Api/ /// <summary> /// 根據緩存Key獲取當前緩存最后更新的標示 /// </summary> /// <param name="cacheKey">緩存Key</param> /// <returns>最后更新的時間</returns> public string getlast(string cacheKey) { List<data> ls = HttpRuntime.Cache.Get(cacheKey) as List<data>; if (ls != null) return ls.First().time; return "0"; } /// <summary> /// 服務器定時方法,這個方法是檢測,數據有沒有進行更新 /// </summary> /// <returns></returns> public ContentResult Listener(string keys) { if (string.IsNullOrWhiteSpace(keys)) return Content("{}"); System.Text.StringBuilder builder = new System.Text.StringBuilder("{"); //客戶端需要知道用戶信息是否變動 if (keys.Contains("msg")) { builder.AppendFormat("\"msg\":\"{0}\",", getlast("msg")); } //客戶端需要知道購買列表是否變動 if (keys.Contains("buy")) { builder.AppendFormat("\"buy\":\"{0}\",", getlast("buy")); } //類推各種監聽....... //移除最后“,” if (builder.Length > 1) { builder.Remove(builder.Length - 1, 1); } builder.Append("}"); return Content(builder.ToString()); } public ContentResult msg(data ms) { InsertCache(ms, "msg"); return Content("ok"); } public ContentResult buy(data buy) { InsertCache(buy, "buy"); return Content("ok"); } private void InsertCache(data d, string cacheKey) { //使用時間設置最后更新量 d.time = DateTime.Now.ToString("yyyyMMddhhmmssffff"); //獲取存儲的值 List<data> ls = HttpRuntime.Cache.Get(cacheKey) as List<data>; //判斷是否為空 if (ls == null) { ls = new List<data>(); HttpRuntime.Cache.Insert(cacheKey, ls); } //添加到集合 ls.Insert(0, d); //移除大於這個數 if (ls.Count > 10) ls.RemoveRange(10, ls.Count - 10); } public JsonResult GetList(string key) { if (string.IsNullOrEmpty(key)) return Json(null, JsonRequestBehavior.AllowGet); return Json(HttpRuntime.Cache.Get(key) as List<data>, JsonRequestBehavior.AllowGet); } public JsonResult GetCache() { return Json(new { p1 = HttpRuntime.Cache.EffectivePercentagePhysicalMemoryLimit, p2 = HttpRuntime.Cache.EffectivePrivateBytesLimit }, JsonRequestBehavior.AllowGet); } } //定義一個數據存儲介質 public class data { public string user { get; set; } public string msg { get; set; } public string time { get; set; } } }
請下載DEMO 點擊下載