客户端与服务器端时间保持一致
来源:互联网 发布:网络政治参与的现状 编辑:程序博客网 时间:2024/05/01 07:01
一、问题描述
需要解决的问题很简单,就是如何在页面上比较准确的显示服务器时间。目前比较常用的方法就是根据基准时间使用setTimeout或 setInterval来计算最新的时间,这样的问题在于setTimeout与setInterval的时间精度比较低,经测试一分钟大概能相差几秒 (与电脑性能以及运行的任务也相关),这样的精度在某些需求下是无法满足的。除此之外,如果要获得比较准确的时间可以定期与服务器进行校准,只是这样实现 的成本大一些。
本文尝试了一种改良的客户端实现时间同步的方式,具有以下的特点:
- 1. 根据基准时间进行纯客户端计算,无需服务器校准
- 2. 时间精度与客户端系统时间保持一致
- 3. 不受客户端时间与服务器时间不同步造成的影响
- 4. 不受客户端系统时间发生修改造成的影响
- 5. 不受页面前进后退造成的影响
二、具体实现
1. 为了解决原方案中的时间精度问题,这里不再使用setTimeout和setInterval来直接计算时间,而是直接使用客户端时间(CT)。不过客户 端时间很可能与服务器时间(ST)不同步,这需要在页面加载的时候计算出客户端与服务器的时间差值(ΔT),这样只需在客户端时间上做一下修正即可得到准 确的服务器时间(ST’ = CT - ΔT)。
2. 由于客户端时间很可能被用户修改,因此直接按照步骤1中的方式计算,一旦用户修改了时间,计算出来的服务器时间也将随之发生变化。这就需要检测出客户端时 间的变化并消除这个变化。检测的方法很简单,即在每个计算周期(T)都将当时的客户端时间(CT2)与上一个周期的客户端时间(CT1)做比较,一旦两个 周期的差值(ΔT’ = CT2 - CT1 - T)大于某个预设值(S)时就将差值(ΔT’)加入到ΔT中,即此时的ΔT = ΔT + ΔT’。之所以需要设置一个预设值,是因为每个周期的时间本身不是固定的(依赖于setTimeout),因此ΔT’并不会等于0,如果每次都将 setTimeout造成的误差作为CT与ST之间的误差将会造成计算不准确。经过以上的计算,用户修改时间后将不会对计算结果产生影响。
3. 经JK提醒,完成以上两步还有一个问题,当用户离开当前页面之后后退回页面时,时间计算不准确。问题在于基准时间是服务器给的,在第一次进入页面的时候确 定,当用户后退回当前页面时,基准时间并没有变,这样会导致重新从过期的基准时间开始计算,导致不准确。需要解决这个问题就是需要解决跨页面的数据存储问 题,这在之前的《Ajax应用中浏览器历史的兼容性解决方案》一 文中已经说明,即通过表单元素来记忆。具体的实现方案是,页面第一次加载时创建两个input,一个用于存储最近一次的客户端时间,一个用于存储最近一次 的基准时间。如果发现已经存在input(前进、后退、非强制刷新)则比较上一次的客户端时间与当前客户端时间,如果其差值大于某个预设值则像步骤2中一 样进行校准,只不过使用的将是最新的基准值。
具体的代码实现如下
/*定义*/ var SyncTimer = (function(){ /*跨页面数据存储器*/ //存储最近一次的客户端时间,用于在页面前进、后退时进行时间矫正 var memoryElementID = 'sync_timer_memory_el'; //存储矫正后的最新基准时间,当页面前进、后退到当前页面时会以此值为新的基准时间 var memoryBaseTimeElementID = 'sync_timer_memory_base_time_el'; document.write(' <input type="text" id="' + memoryElementID + '">'); document.write(' <input type="text" id="' + memoryBaseTimeElementID + '">'); return{ /* * @param { Integer } baseTime 基准时间 * @param { Function } updater 时间更新时的监听器 * @param { Integer } interval 校准计算周期时长,默认为200ms。 * @param { Integer } threshold 两个检查周期之间的时间误差(差值-周期时长)如果大于阈值则视为客户端时间有调整,默认为500ms。 */ run: function(baseTime,updater,interval,threshold){ interval = interval || 200; threshold = threshold || 500; var memoryEl = document.getElementById(memoryElementID); var baseTimeEl = document.getElementById(memoryBaseTimeElementID); /*前进、后退或刷新,则矫正baseTime*/ if( memoryEl.value != '' ){ //计算当前客户端时间与上次存储的客户端时间之差,如果差值超过阈值则更新基准时间 var diff = +new Date - parseInt(memoryEl.value); if( Math.abs( diff ) - interval > threshold ){ baseTime = parseInt(baseTimeEl.value); baseTime += diff; } } var ct = +new Date; var diff = ct - baseTime; var pt = ct,cct; (function(){ cct = +new Date; /*计算当前计算周期与上一个计算周期的时间差,如果差值大于设定的阈值则进行矫正(处理客户端时间调整的情况)*/ var secDiff = cct - pt; if( Math.abs( secDiff ) - interval > threshold ){ diff += (secDiff - interval); } var fixedTime = cct - diff; updater( fixedTime ); pt = memoryEl.value = cct; baseTimeEl.value = fixedTime; setTimeout(arguments.callee,interval); })(); } } })(); /*使用*/ window.onload = function(){ var serverTime = parseInt($('dateWrapper').getAttribute('date'))*1000; SyncTimer.run(serverTime,function(date){ var d = new Date(date); $('dateWrapper').innerHTML = d.format('yyyy-MM-dd hh:mm:ss'); $('dateWrapper').setAttribute('date',parseInt(date/1000)); }); }
三、总结
- 总体实现还是比较麻烦,如果对时间精度要求不高可不必这么做。
- 还有一种情况未解决:用户从当前页面进入别的页面后修改客户端时间,之后后退到当前页面,此时时间计算不正确,但是暂时未找到解决方案。
- 此外发现两个有意思的东西:1. 在Firefox下如果将客户端时间改慢会导致setInterval停止运行,而setTimeout则不会;2. 在Chrome中,当用户修改了客户端时间后,setInterval中取到的Date的值并不会随用户的修改而修改。
- 下面写上我修改后的代码,可以直接粘贴使用的倒计时代码,而不是系统时间的代码
<script>/*定义*/ var SyncTimer = (function(){ /*跨页面数据存储器*/ //存储最近一次的客户端时间,用于在页面前进、后退时进行时间矫正 var memoryElementID = 'sync_timer_memory_el'; //存储矫正后的最新基准时间,当页面前进、后退到当前页面时会以此值为新的基准时间 var memoryBaseTimeElementID = 'sync_timer_memory_base_time_el'; document.write('<input type="hidden" id="' + memoryElementID + '">'); document.write('<input type="hidden" id="' + memoryBaseTimeElementID + '">'); return{ /* * @param { Integer } baseTime 基准时间 * @param { Function } updater 时间更新时的监听器 * @param { Integer } interval 校准计算周期时长,默认为200ms。 * @param { Integer } threshold 两个检查周期之间的时间误差(差值-周期时长)如果大于阈值则视为客户端时间有调整,默认为500ms。 */ run: function(baseTime,updater,interval,threshold){ interval = interval || 200; threshold = threshold || 500; var memoryEl = document.getElementById(memoryElementID); var baseTimeEl = document.getElementById(memoryBaseTimeElementID); /*前进、后退或刷新,则矫正baseTime*/ if( memoryEl.value != '' ){ //计算当前客户端时间与上次存储的客户端时间之差,如果差值超过阈值则更新基准时间 var diff = +new Date - parseInt(memoryEl.value); if( Math.abs( diff ) - interval > threshold ){ baseTime = parseInt(baseTimeEl.value); baseTime += diff; } } var ct = +new Date; var diff = ct - baseTime; var pt = ct,cct; (function(){ cct = +new Date; /*计算当前计算周期与上一个计算周期的时间差,如果差值大于设定的阈值则进行矫正(处理客户端时间调整的情况)*/ var secDiff = cct - pt; if( Math.abs( secDiff ) - interval > threshold ){ diff += (secDiff - interval); } var fixedTime = cct - diff; updater( fixedTime ); pt = memoryEl.value = cct; baseTimeEl.value = fixedTime; setTimeout(arguments.callee,interval); })(); } } })(); /*使用*/ window.onload = function(){ var serverTime = parseInt({$time})*1000; SyncTimer.run(serverTime,function(date){ var intDiff = (1437364800+18000)*1000 - date;intDiff = Math.floor(intDiff / 1000);var day=0,hour=0,minute=0,second=0;//时间默认值if(intDiff > 0){day = Math.floor(intDiff / (60 * 60 * 24));hour = Math.floor(intDiff / (60 * 60)) - (day * 24);minute = Math.floor(intDiff / 60) - (day * 24 * 60) - (hour * 60);second = Math.floor(intDiff) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60);}if (minute <= 9) minute = '0' + minute;if (second <= 9) second = '0' + second;-$('#day_show').html(day+"天");$('#hour_show').html('<s id="h"></s>'+hour+'时');$('#minute_show').html('<s></s>'+minute+'分');$('#second_show').html('<s></s>'+second+'秒');if(minute == '00' && second == '00' && hour == '0' && day == '0'){temp = window.clearInterval(temp);$('#now').removeClass('btn-danger');$('#now').addClass('btn-success');} }); } </script>
- 客户端与服务器端时间保持一致
- Android 客户端与服务器端时间校准
- PHP 时间戳与系统时间保持一致
- 关于客户端与数据库服务器端的时间同步问题
- 关于客户端与数据库服务器端的时间同步问题
- linux ntpdate客户端与ntpd服务器端设置时间同步
- 客户端与服务器端通信
- 客户端与服务器端
- 客户端与服务器端
- 一、客户端与服务器端例程
- 客户端与服务器端的通信
- red5 服务器端与客户端实例
- 客户端跳转与服务器端跳转
- 客户端与服务器端交互原理
- 客户端跳转与服务器端跳转
- 服务器端与客户端的编程
- VNC服务器端与客户端配置
- Android客户端与电脑服务器端
- 二分图的最大匹配、完美匹配和匈牙利算法
- 反转单链表
- 《深入浅出struts2》--第二章,初识struts
- java之网络协议初探和Socket的使用实践
- wifi direct
- 客户端与服务器端时间保持一致
- android下ndk编译ffmpeg
- C++中替代sprintf的std::ostringstream输出流详解
- Ubuntu 12.04环境搭建
- SpringMVC学习笔记(二) -- 提交数据至后台controller
- C++ HOJ 火车进站
- 关于一些UI的property应该使用retain、strong还是weak的问题
- MySQL用户创建与授权
- 修改app的默认设置(包括修改默认launcher)