通過NTP協議進行時間同步


最近發現手機的時間不是很准了,便到網上下了一個同步時間的小程序,簡單了看了一下它的原理,是通過NTP協議來實現校時的,就順便學習了一下NTP協議,用C#寫了個簡單的實現。

NTPNetwork Time Protocol,網絡時間協議)是由RFC 1305定義的時間同步協議,用來在分布式時間服務器和客戶端之間進行時間同步。

NTP工作原理

NTP的基本工作原理如下圖所示。Device ADevice B通過網絡相連,它們都有自己獨立的系統時鍾,需要通過NTP實現各自系統時鍾的自動同步。為便於理解,作如下假設:

  • Device ADevice B的系統時鍾同步之前,Device A的時鍾設定為10:00:00amDevice B的時鍾設定為11:00:00am
  • Device B作為NTP時間服務器,即Device A將使自己的時鍾與Device B的時鍾同步。
  • NTP報文在Device ADevice B之間單向傳輸所需要的時間為1秒。

        

   

  • Device A發送一個NTP報文給Device B,該報文帶有它離開Device A時的時間戳,該時間戳為10:00:00amT1)。
  • 當此NTP報文到達Device B時,Device B加上自己的時間戳,該時間戳為11:00:01amT2)。
  • 當此NTP報文離開Device B時,Device B再加上自己的時間戳,該時間戳為11:00:02amT3)。
  • Device A接收到該響應報文時,Device A的本地時間為10:00:03amT4)。

至此,Device A已經擁有足夠的信息來計算兩個重要的參數:

  • NTP報文的往返時延Delay=T4-T1-T3-T2=2秒。
  • Device A相對Device B的時間差offset=((T2-T1+T3-T4))/2=1小時。

NTP的報文格式

NTP有兩種不同類型的報文,一種是時鍾同步報文,另一種是控制報文(僅用於需要網絡管理的場合,與本文無關,這里不做介紹)。

NTP基於UDP報文進行傳輸,使用的UDP端口號為123;時鍾同步報文封裝在UDP報文中,其格式如下圖所示。

        

   

主要字段的解釋如下:

  • LILeap Indicator):長度為2比特,值為"11"時表示告警狀態,時鍾未被同步。為其他值時NTP本身不做處理。
  • VNVersion Number):長度為3比特,表示NTP的版本號,目前的最新版本為3
  • Mode:長度為3比特,表示NTP的工作模式。不同的值所表示的含義分別是:0未定義、1表示主動對等體模式、2表示被動對等體模式、3表示客戶模式、4表示服務器模式、5表示廣播模式或組播模式、6表示此報文為NTP控制報文、7預留給內部使用。
  • Stratum:系統時鍾的層數,取值范圍為116,它定義了時鍾的准確度。層數為1的時鍾准確度最高,准確度從116依次遞減,層數為16的時鍾處於未同步狀態,不能作為參考時鍾。
  • Poll:輪詢時間,即兩個連續NTP報文之間的時間間隔。
  • Precision:系統時鍾的精度。
  • Root Delay:本地到主參考時鍾源的往返時間。
  • Root Dispersion:系統時鍾相對於主參考時鍾的最大誤差。
  • Reference Identifier:參考時鍾源的標識。
  • Reference Timestamp:系統時鍾最后一次被設定或更新的時間。
  • Originate TimestampNTP請求報文離開發送端時發送端的本地時間。
  • Receive TimestampNTP請求報文到達接收端時接收端的本地時間。
  • Transmit Timestamp:應答報文離開應答者時應答者的本地時間。
  • Authenticator:驗證信息。

NTP時間同步的實現

有了上述基礎知識后,我們就可以實現自己的時間同步工具了,下文附了一個簡單的C#的實現。

    class NptClient

    {

        IPAddress ntpServer;

        public NptClient(IPAddress ntpServer)

        {

            this.ntpServer = ntpServer;

        }

   

        public DateTime GetServerTime()

        {

            var startTime = DateTime.Now;

            var ntpTime = NTPData.Test(ntpServer);

            var recvTime = DateTime.Now;

   

            var offset = ((ntpTime.ReceiveTimestamp - startTime) + (ntpTime.TransmitTimestamp - recvTime));

            offset = offset.Subtract(TimeSpan.FromSeconds(offset.TotalSeconds / 2));

   

            return recvTime + offset;

        }

    }

   

    [StructLayout(LayoutKind.Sequential)]

    class NTPData

    {

        byte header = 0;

        byte Stratum = 1;           //系統時鍾的層數,取值范圍為116,它定義了時鍾的准確度

        byte Poll = 1;              //輪詢時間,即兩個連續NTP報文之間的時間間隔

        byte Precision = 1;         //系統時鍾的精度

        BigEndianUInt32 rootDelay;

        BigEndianUInt32 referenceIdentifier;

        BigEndianUInt32 ReferenceIdentifier;

   

        public NtpTime ReferenceTimestamp { getprivate set; }

        public NtpTime OriginateTimestamp { getprivate set; }

        public NtpTime ReceiveTimestamp { getprivate set; }

        public NtpTime TransmitTimestamp { getprivate set; }

   

        public NTPData()

        {

            this.header = GetHeader();

        }

   

        byte GetHeader()

        {

            var LI = "00";

            var VN = "011";         //NTP的版本號為3

            var Mode = "011";       //客戶模式

   

            return Convert.ToByte(LI + VN + Mode, 2);

        }

   

        public static NTPData Test(IPAddress ntpServer)

        {

            var data = MarshalExtend.GetData(new NTPData());

   

            var udp = new System.Net.Sockets.UdpClient();

            udp.Send(data, data.Length, new IPEndPoint(ntpServer, 123));

   

            var ep = new IPEndPoint(IPAddress.Any, 0);

            var replyData = udp.Receive(ref ep);

   

            return MarshalExtend.GetStruct<NTPData>(replyData, replyData.Length);

        }

    }

   

    [StructLayout(LayoutKind.Sequential)]

    class NtpTime

    {

        BigEndianUInt32 seconds;

        BigEndianUInt32 fraction;

   

        static readonly DateTime baseTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);

   

        public static implicit operator DateTime(NtpTime time)

        {

            /* rfc1305ntp時間中,時間是用64bit來表示的,記錄的是1900年后的秒數(utc格式)

             * 32位是整數部分,低32位是小數部分 */

   

            var milliseconds = (int)(((double)time.fraction / uint.MaxValue) * 1000);

            return baseTime.AddSeconds(time.seconds).AddMilliseconds(milliseconds).ToLocalTime();

        }

   

        public override string ToString()

        {

            return ((DateTime)this).ToString("o");

        }

    }

   

   

當然,我這里只是在造重復輪子,網上是有不少功能完整的開源項目的。另外,如果對SNTPv4RFC 2030)感興趣的,可以參考一下這個頁面上的實現——Simple Network Time (NTP) Protocol Client

最后,附上幾個可以使用(不保證,具體能用否還得看電信和方校長的心情)的NTP服務器:

  • 133.100.11.8 prefer
  • 210.72.145.44
  • 203.117.180.36
  • 131.107.1.10
  • time.asia.apple.com
  • 64.236.96.53
  • 130.149.17.21
  • 66.92.68.246
  • 18.145.0.30
  • clock.via.net
  • 137.92.140.80
  • 133.100.9.2
  • 128.118.46.3
  • ntp.nasa.gov
  • 129.7.1.66ntp-sop.inria.frserver
  • 210.72.145.44(中國國家授時中心服務器IP地址)
  • ntp.sjtu.edu.cn (上海交通大學網絡中心NTP服務器地址)
  • 202.120.2.101 (上海交通大學網絡中心NTP服務器地址)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM