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中一 樣進行校準,只不過使用的將是最新的基準值。

具體的程式碼實現如下

  1. /*定義*/
  2.     var SyncTimer = (function(){    
  3.         /*跨頁面資料儲存器*/
  4.         //儲存最近一次的客戶端時間,用於在頁面前進、後退時進行時間矯正  
  5.         var memoryElementID = 'sync_timer_memory_el';    
  6.         //儲存矯正後的最新基準時間,當頁面前進、後退到當前頁面時會以此值為新的基準時間  
  7.         var memoryBaseTimeElementID = 'sync_timer_memory_base_time_el';    
  8.         document.write('   
  9. <input type="text" id="' + memoryElementID + '">');    
  10.         document.write('   
  11. <input type="text" id="' + memoryBaseTimeElementID + '">');    
  12.         return{    
  13.             /*  
  14.              * @param { Integer } baseTime 基準時間  
  15.              * @param { Function } updater 時間更新時的監聽器  
  16.              * @param { Integer } interval 校準計算週期時長,預設為200ms。  
  17.              * @param { Integer } threshold 兩個檢查週期之間的時間誤差(差值-週期時長)如果大於閾值則視為客戶端時間有調整,預設為500ms。  
  18.              */
  19.             run: function(baseTime,updater,interval,threshold){    
  20.                 interval = interval || 200;    
  21.                 threshold = threshold || 500;                       
  22.                 var memoryEl = document.getElementById(memoryElementID);    
  23.                 var baseTimeEl = document.getElementById(memoryBaseTimeElementID);    
  24.                 /*前進、後退或重新整理,則矯正baseTime*/
  25.                 if( memoryEl.value != '' ){    
  26.                     //計算當前客戶端時間與上次儲存的客戶端時間之差,如果差值超過閾值則更新基準時間  
  27.                     var diff = +new Date - parseInt(memoryEl.value);    
  28.                     if( Math.abs( diff ) - interval > threshold ){    
  29.                         baseTime = parseInt(baseTimeEl.value);    
  30.                         baseTime += diff;    
  31.                     }    
  32.                 }    
  33.                 var ct = +new Date;    
  34.                 var diff = ct - baseTime;    
  35.                 var pt = ct,cct;    
  36.                 (function(){    
  37.                     cct = +new Date;    
  38.                     /*計算當前計算週期與上一個計算週期的時間差,如果差值大於設定的閾值則進行矯正(處理客戶端時間調整的情況)*/
  39.                     var secDiff = cct - pt;    
  40.                     if( Math.abs( secDiff ) - interval > threshold ){    
  41.                         diff += (secDiff - interval);    
  42.                     }    
  43.                     var fixedTime = cct - diff;    
  44.                     updater( fixedTime );    
  45.                     pt = memoryEl.value = cct;    
  46.                     baseTimeEl.value = fixedTime;    
  47.                     setTimeout(arguments.callee,interval);    
  48.                 })();    
  49.             }    
  50.         }    
  51.     })();    
  52.     /*使用*/
  53.     window.onload = function(){    
  54.         var serverTime = parseInt($('dateWrapper').getAttribute('date'))*1000;    
  55.         SyncTimer.run(serverTime,function(date){    
  56.             var d = new Date(date);    
  57.             $('dateWrapper').innerHTML = d.format('yyyy-MM-dd hh:mm:ss');    
  58.             $('dateWrapper').setAttribute('date',parseInt(date/1000));    
  59.         });    
  60.     }    

三、總結

  • 總體實現還是比較麻煩,如果對時間精度要求不高可不必這麼做。
  • 還有一種情況未解決:使用者從當前頁面進入別的頁面後修改客戶端時間,之後後退到當前頁面,此時時間計算不正確,但是暫時未找到解決方案。
  • 此外發現兩個有意思的東西:1. 在Firefox下如果將客戶端時間改慢會導致setInterval停止執行,而setTimeout則不會;2. 在Chrome中,當用戶修改了客戶端時間後,setInterval中取到的Date的值並不會隨使用者的修改而修改。

相關推薦

客戶實現正確伺服器時間同步

一、問題描述 需要解決的問題很簡單,就是如何在頁面上比較準確的顯示伺服器時間。目前比較常用的方法就是根據基準時間使用setTimeout或 setInterval來計算最新的時間,這樣的問題在於setTimeout與setInterval的時間精度比較低,經測試一分鐘大概能相差幾秒 (與電腦效能以及執行

關於客戶與資料庫伺服器時間同步問題

這是一個做C/S的管理軟體開發時經常被忽略的問題,客戶端的時間與伺服器的時間如果有偏差,資料統計、報表等等肯定會有“意外”的情況發生。 意圖很簡單:從資料庫伺服器獲取到時間,根據這個時間修改當前客戶端電腦時間。 用Sql的函式getdate(),是比較容易的。 我們是基於do

收發檔案的伺服器/客戶實現

程式需求 客戶端接受使用者輸入的傳輸檔名 客戶端請求伺服器端傳輸該檔名所指檔案 伺服器端程式碼: #include <iostream> #include <string> #include <stdlib.h> #include

TCP 伺服器/客戶(實現下載)

TCP/IP :       TCP/IP:在網路通訊中,TCP/IP是主流協議()       應用層:使用者自定義的協議(HTTP,EMAIL,),用於使用者之間資料的傳送      &nbs

記筆記:C# Socket客戶監聽伺服器處理方案【同步

方案主要功能:        (1)客戶端同步監聽來自伺服器端的資料(開啟子執行緒監聽)        (2)客戶端向伺服器端傳送資料(主執行緒傳送,並控制)        

Android-使用者登陸的例子(伺服器開發到客戶實現

1.回顧        上篇實現總結了App介面開發課程講解的內容 2.此篇      這篇將總結app介面開發裡的 使用者登入的例子! 3.基本步驟     (1)服務端實現 登陸介面 開發     (2)實現 json 和xml資料封裝類     (3)編寫 介面開

Xshell 5+lrzsz實現Windows客戶向CentOS伺服器上傳檔案

安裝lrzsz 在centost的命令列上執行命令: yum install lrzsz 安裝完成後,可以進入需要上傳檔案的目錄,然後執行命令: rz 這時會彈出一個上傳檔案的視窗,選擇需要上傳的檔案即可上傳。

JAVA NIO 伺服器客戶實現示例(程式碼1)

公共類: [java] view plain copy print? package com.stevex.app.nio;  import java.nio.ByteBuffer;  import java.nio.CharBuffer;  import j

網路程式設計——用執行緒實現可供多客戶訪問的伺服器

#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #includ

android客戶+JAVA WEB伺服器實現json資料解析

import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.But

網路程式設計——用程序實現可供多客戶訪問的伺服器

#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #includ

JAVA NIO 伺服器客戶實現示例

公共類: package com.stevex.app.nio; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingExcepti

Android客戶與PC伺服器實現Socket通訊(wifi)

本文介紹Android終端持續掃描AP資訊併發送給伺服器端的實現。首先基於TCP協議在Android終端和PC兩端之間形成網路虛擬鏈路。使用ServerSocket建立TCP伺服器端,然後在Android客戶端使用Socket的構造器來連線伺服器。其中Android終端通過WIFI連線和PC處於同一區域網

Android客戶+mysql+springmvc伺服器實現登陸的小案例

首先是客戶端 通過輸入使用者名稱+密碼實現登入 點選登入後向伺服器傳送http請求 伺服器收到請求後驗證使用者名稱密碼是否與mysql資料庫上的相應欄位是否一致 然後返回json資料 客戶端獲取響應的結果 然後提醒是否登入成功 MainActivity程式碼: public

騰訊雲Linux伺服器搭建(九) Linux上DB2的客戶實現資料的備份和恢復

問題:DB2伺服器裝在AIX上,不允許直接利用伺服器做資料的匯出匯入。但是由於專案需要,需要頻繁的對一些表進行資料的備份和恢復。所以只能在一個其他伺服器上安裝DB2客戶端,通過客戶端來實現資料的備份和恢復。 思路:先從IBM官網下載客戶端;然後安裝到一個應用伺服器上(Cen

Java NIO 伺服器客戶實現檔案下載

import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.chann

Javascript實現商品秒殺倒計時(時間伺服器時間同步

現在有很多網站都在做秒殺商品,而這其中有一個很重要的環節就是倒計時。 關於倒計時,有下面幾點需要注意: 1.應該使用伺服器時間而不是本地時間(本地時間存在時區不同、使用者自行設定等問題)。 2.要考慮網路傳輸的耗時。 3.獲取時間時可直接從AJAX的響應頭中讀取(通過get

實現Android客戶與Eclipse伺服器基於Okthhp簡單通訊

最近在重溫知識,所以藉此機會也想把自己寫的一些心得寫出來供大家分享,寫的有誤或者不好的地方望大家見諒,好了,廢話少說,直接進入正題,下面給大家介紹的就是基於目前主流網路通訊框架的okhttp實現的Android與Eclipse通訊。首先說明:我用的Android客戶端是And

Android客戶與Tomcat伺服器通訊實現登入驗證

一.功能描述   在Android客戶端實現登入介面,當將使用者名稱和密碼填入文字框並點選登入按鈕時,將認證資訊傳送至Tomcat伺服器進行認證,若使用者名稱和密碼匹配,則Android客戶端提示登入成功,否則提示登陸失敗。 二.開發環境 Android客戶端:Andro

Javascript實現秒殺倒計時(時間伺服器時間同步

現在有很多網站都在做秒殺商品,而這其中有一個很重要的環節就是倒計時。 關於倒計時,有下面幾點需要注意: 1.應該使用伺服器時間而不是本地時間(本地時間存在時區不同、使用者自行設定等問題)。 2.要考慮網路傳輸的耗時。 3.獲取時間時可直接從AJAX的響應頭中讀取(通過getResponseHeader('D