客戶端與伺服器端時間保持一致
一、問題描述
需要解決的問題很簡單,就是如何在頁面上比較準確的顯示伺服器時間。目前比較常用的方法就是根據基準時間使用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>
相關推薦
客戶端與伺服器端時間保持一致
一、問題描述 需要解決的問題很簡單,就是如何在頁面上比較準確的顯示伺服器時間。目前比較常用的方法就是根據基準時間使用setTimeout或 setInterval來計算最新的時間,這樣的問題在於setTimeout與setInterval的時間精度比較低,經測試一分鐘大概能相差幾秒 (與電腦效能以及執行
APP(Android版)客戶端與伺服器端時間校準
APP開發人員經常會遇見一個bug就是,APP顯示的時間不準,或者說APP時間與伺服器時間不一致,會導致資料請求、資料顯示等各種問題。這時候我們就需要一種機制來解決時間不一致的問題。 解決方案如下: 1.伺服器端永遠使用UTC時間,包括引數和返回值,不要使用Date格式,而是使用UT
android客戶端與伺服器端互動 如何保持session
最近在開發專案的過程中,遇到android與web伺服器要在同一session下通訊的問題。 在解決問題前先回顧下Session與Cookie: Cookie和Session都為了用來儲存狀態資訊,都是儲存客戶端狀態的機制,它們都是為了解決HTTP無狀態的問題而所做
配置ntp客戶端與伺服器端時間的同步
1,實驗機器介紹 Ip地址 伺服器1 192.168.245.128 伺服器2 192.168.245.130 客戶端1 192.1
如何在HTTP客戶端與伺服器端之間保持狀態
HTTP協議與狀態保持 HTTP協議本身是無狀態的,這與HTTP協議本來的目的是相符的,客戶端只需要簡單的向伺服器請求下載某些檔案,無論是客戶端還是伺服器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員
如何在HTTP客戶端與伺服器端之間保持狀態 ?總結筆記
1、Http協議本身是無狀態的,每一次請求都是獨立的。 2、為了使web變得更加方便,儲存狀態,就出現了cookie與session cookie機制採用的是在客戶端保持狀態的方案,ses
Android客戶端與伺服器端RSA加密通訊加密字元不一致相關問題
RSA非對稱加密演算法 ,適用於資料量較小的情況,比如一般的用法: 1、生成RSA金鑰對,公鑰發給客戶端使用,私鑰由伺服器使用; 2、客戶端用公鑰加密所有發出的資料,也用公鑰解密所有收到的資料; 3、伺服器用私鑰加密所有發出的資料,也用私鑰解密所有收到的資料; 但Andro
Socket-tcp協議客戶端與伺服器端互聯
客戶端 using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.T
zookeeper叢集的客戶端與伺服器端
zookeeper服務端命令: 啟動命令:sh zkServer.sh start 停止命令:sh zkServer.sh stop zookeeper客戶端命令: 啟動命令:sh zkCli.sh 連線其他客戶端:sh zkCli.sh -server ip:port  
php 客戶端與伺服器端安全與破解
一般的加密和授權:轉發伺服器(代理伺服器) 解決方案:hhvm編譯程式碼 放扒取: js類 1:防止滑鼠右鍵事件,在html->body <body oncontextmenu=self.event.returnValue=false> 或
java socket:客戶端與伺服器端通訊
Socket:網路上兩個程式通過一個雙向的通訊連線實現資料交換,連線的一段為一個socket,要實現兩個程式的資料交換一般要一對socket。 這個定義參考自百度百科,我覺得說的還不錯,另外,socket的英文有‘插口’的意思,其實也可以理解為程式的插口等等。
客戶端與伺服器端建立連線的過程
一、概述 學習計算機其實就是在通曉原理的基礎上藉助實踐驗證想法。王陽明的“知行合一”用在計算機上,也是十分的貼切。這裡先說明兩個概念 Socket、TCP。 “交流”讓智人走上食物鏈的頂端。計算機網路的發展讓交流變得更加便利,同時也促進交流技術的發展。如果有兩個網友想送
通俗易懂客戶端與伺服器端互動原理(HTTP資料請求與HTTP響應,包括Servlet部分)
經常看到HTTP客戶端與伺服器端互動原理的各種版本的文章,但是專業術語太多,且流程過於複雜,不容易消化。於是就按照在 Servlet 裡面的內容大致做了一些穿插。本來 連 Tomcat 容器 和 Servlet 的生命週期也準備在這裡一起寫的,但怕過去龐大,於是就簡單的 引用
客戶端與伺服器端互動原理簡述
Web 瀏覽器(客戶端)中請求一個地址時,通過HTTP協議向伺服器端傳送一個請求(request),伺服器端收到請求後,在Servlet中根據請求時的方法(method) 的設定get/post來響應doGet()/doPost()方法進行處理,由respons
node建立客戶端與伺服器端(HTTP)
Transfer-Encoding: chunked 在我用telnet登入伺服器的時候,伺服器返回資訊如下。 其中,我們看到這樣一條相應–Transfer-Encoding: chunked。Transfer-Encoding頭資訊的預設值是c
Android 客戶端與伺服器端進行資料互動(一、登入伺服器端)
概要 安卓APP要實現很多功能(比如登入註冊、發表評論等)時都必須要使用到網路資料互動。所以在學習了這部分內容後,就將其以最常見的登入過程為例整理出來,也方便跟我一樣的新手能迅速學習上手。 預期效果圖如下,輸入手機號和密碼,點選Login按鈕,上傳資料到伺
Java Socket通訊 客戶端與伺服器端的連線 和資訊傳輸
一.建立客戶端和伺服器端的連線。 方法分2步: 1.在客戶端建立Socket物件,用來發送連線請求 2.在伺服器端穿件ServerSocket物件,用來接收請求。 //客戶端 public clas
Android客戶端與伺服器端的json資料互動(很詳細)
Android客戶端與伺服器端的json資料互動,主要是通過json形式的資料互動,就是json的寫入和解析。 先看效果圖,我最討厭講東西,一個圖沒有的。 算了,看來我不是寫部落格的材料,寫不下去了,要排版之類的麻煩,大家還是直接去下載原始碼,裡面有大量的注
SVN:客戶端與伺服器端安裝、配置與使用
SVN(SUBVERSION) 前言: SVN是Subversion的簡稱,是一個開放原始碼的版本控制系統,說得簡單一點SVN就是用於多個人共同開發同一個專案,共用資源的目的。類似的工具號有git(github是其應用)。 環境:ubuntu 16.4 客戶端:w
Android 客戶端與伺服器端進行資料互動(二、登入客戶端)
概要 Android客戶端分為User,HttpUtil,HttpCallbackListener,MainActivity四個部分。User model與服務端的一樣,一方面是用於本地使用者資訊的儲存model,另一方面也是為了保證構造URL時使用的key一