1. 程式人生 > >客戶端與伺服器端時間保持一致

客戶端與伺服器端時間保持一致

一、問題描述

需要解決的問題很簡單,就是如何在頁面上比較準確的顯示伺服器時間。目前比較常用的方法就是根據基準時間使用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一