Web网站数据”实时”更新设计
请注意这个实时打上了双引号,没有绝对的实时,只是时间的颗粒不一样罢了(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 "";
}
/// <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 > ) {
builder.Remove(builder.Length - , );
}
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(, d);
//移除大于这个数
if (ls.Count > )
ls.RemoveRange(, ls.Count - );
}
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 点击下载
Web网站数据”实时”更新设计的更多相关文章
- 使用php+swoole对client数据实时更新(下)
上一篇提到了swoole的基本使用,现在通过几行基本的语句来实现比较复杂的逻辑操作: 先说一下业务场景.我们目前的大多数应用都是以服务端+接口+客户端的方式去协调工作的,这样的好处在于不论是处在何种终 ...
- 使用php+swoole对client数据实时更新(二) (转)
上一篇提到了swoole的基本使用,现在通过几行基本的语句来实现比较复杂的逻辑操作: 先说一下业务场景.我们目前的大多数应用都是以服务端+接口+客户端的方式去协调工作的,这样的好处在于不论是处在何种终 ...
- MySQL 实现将一个库表里面的数据实时更新到另一个库表里面
MySQL 实现将一个库表里面的数据实时更新到另一个库表里面 需求描述:MySQL 里面有很多的数据库,这些数据库里面都有同一种表结构的表 (tb_warn_log),这张表的数据是实时更新的,现在需 ...
- 基于server broker 的数据实时更新
Service Broker介绍:SQL Server Service Broker 为消息和队列应用程序提供 SQL Server 数据库引擎本机支持.这使开发人员可以轻松地创建使用数据库引擎组件在 ...
- 使用php+swoole对client数据实时更新(上)
如果想对一个列表做实时的更新,传统的做法是采用轮询的方式.以web为例,通过Ajax定时请求服务端然后获取数据显示在页面.这种方式实现简单,缺点就是浪费资源. HTTP1.1新增加了对websocke ...
- vue 跳转并传参,实现数据实时更新
原文链接:点我 比如我现在在页面A跳转到页面B,A中的router-link :to={path:’B’,params:{id:’5’}} 求助:在页面B中的mounted生命周期函数中使用this. ...
- MFC编辑框数据实时更新问题
在VC里,很多情况下需要更新控件,也就是调用UpdateData(FALSE);但是如果是在循环中调用该函数,会导致没有时间来刷新界面,消息得不到相应,从外部看来,似乎整个循环只执行了一次Update ...
- IDEA 修改JSP和后端数据后,页面刷新可以实时更新
情况:刚开始使用IDEA进行开发时,发现修改JSP页面或者后端数据后,再刷新浏览器页面,发现没有变化,页面无更新. 这样就导致不得不频繁重启tomcat服务器.非常麻烦 解决方法: 步骤1. 先设置t ...
- Tornado实现监控数据实时展示
前言: It has been a while since I last updated my blogs. 使用Tornado开发一个实时监控信息系统,其中包括 CUP.内存.网卡.磁盘使用率. 涉 ...
随机推荐
- 学习使用:before和:after伪元素
如果你一直密切关注着各种网页设计的博客,你可能已经注意到了:before和:after伪元素已经在前端开发中获得了相当多的关注.特别是在Nicolas Gallagher的博客中,后期运用了很多伪类元 ...
- IPv4分析
IPv4的头部格式: 1. Version 版本号,默认是4. 2. IHL(Internet Header Length) 就是IPv4头部长度.这个长度的单位是32bit,一般是5,那么头部的长度 ...
- IOS Application Security Testing Cheat Sheet
IOS Application Security Testing Cheat Sheet [hide] 1 DRAFT CHEAT SHEET - WORK IN PROGRESS 2 Int ...
- [转载]斐讯K2 A2版免TTL刷BREED不死Bootloader
1:路由器能正常上网,登陆进路由器 (默认密码admin) 2:用浏览器打开这个网址,如果默认ip不是192.168.2.1修改一下.打开后等待即可自动刷入breed. http://192.168. ...
- 二十、【.Net开源】EFW框架核心类库之WebService服务
回<[开源]EFW框架系列文章索引> EFW框架源代码下载V1.1:http://pan.baidu.com/s/1qWJjo3U EFW框架实例源代码下载:http://pan.baid ...
- Android向系统相册中插入图片,相册中会出现两张 一样的图片(只是图片大小不一致)
向系统相册中插入图片调用此方法时,相册中会出现两张一样的图片 MediaStore.Images.Media.insertImage 一张图片是原图一张图片是缩略图.表现形式为:android4.4. ...
- [LeetCode] 桶排序的特殊解,例 Sort Color
Sort Colors Given an array with n objects colored red, white or blue, sort them so that objects of t ...
- 测试GeoGebra博客
已知函数 \(\textit{f}(\textit{x})=2\textit{m}\ln\textit{x}-\textit{x}^2\), \(\textit{g}(\textit{x})=\tex ...
- RESTful API 设计指南【转】
网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备......). 因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信.这导致AP ...
- 你需要知道的Sass插值
你也许会不时地写写 Sass 玩玩,你也会很享受它带给你各种便利.但还有一件事,你并不一定完全了解:插值 (interpolation) - 将一个占位符,替换成一个值.好了,你们都很幸运,因为今天我 ...