最近發現手機的時間不是很准了,便到網上下了一個同步時間的小程序,簡單了看了一下它的原理,是通過NTP協議來實現校時的,就順便學習了一下NTP協議,用C#寫了個簡單的實現。
NTP(Network Time Protocol,網絡時間協議)是由RFC 1305定義的時間同步協議,用來在分布式時間服務器和客戶端之間進行時間同步。
NTP工作原理
NTP的基本工作原理如下圖所示。Device A和Device B通過網絡相連,它們都有自己獨立的系統時鍾,需要通過NTP實現各自系統時鍾的自動同步。為便於理解,作如下假設:
-
在Device A和Device B的系統時鍾同步之前,Device A的時鍾設定為10:00:00am,Device B的時鍾設定為11:00:00am。
-
Device B作為NTP時間服務器,即Device A將使自己的時鍾與Device B的時鍾同步。
-
NTP報文在Device A和Device B之間單向傳輸所需要的時間為1秒。

-
Device A發送一個NTP報文給Device B,該報文帶有它離開Device A時的時間戳,該時間戳為10:00:00am(T1)。
-
當此NTP報文到達Device B時,Device B加上自己的時間戳,該時間戳為11:00:01am(T2)。
-
當此NTP報文離開Device B時,Device B再加上自己的時間戳,該時間戳為11:00:02am(T3)。
-
當Device A接收到該響應報文時,Device A的本地時間為10:00:03am(T4)。
至此,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報文中,其格式如下圖所示。
主要字段的解釋如下:
-
LI(Leap Indicator):長度為2比特,值為"11"時表示告警狀態,時鍾未被同步。為其他值時NTP本身不做處理。
-
VN(Version Number):長度為3比特,表示NTP的版本號,目前的最新版本為3。
-
Mode:長度為3比特,表示NTP的工作模式。不同的值所表示的含義分別是:0未定義、1表示主動對等體模式、2表示被動對等體模式、3表示客戶模式、4表示服務器模式、5表示廣播模式或組播模式、6表示此報文為NTP控制報文、7預留給內部使用。
-
Stratum:系統時鍾的層數,取值范圍為1~16,它定義了時鍾的准確度。層數為1的時鍾准確度最高,准確度從1到16依次遞減,層數為16的時鍾處於未同步狀態,不能作為參考時鍾。
-
Poll:輪詢時間,即兩個連續NTP報文之間的時間間隔。
-
Precision:系統時鍾的精度。
-
Root Delay:本地到主參考時鍾源的往返時間。
-
Root Dispersion:系統時鍾相對於主參考時鍾的最大誤差。
-
Reference Identifier:參考時鍾源的標識。
-
Reference Timestamp:系統時鍾最后一次被設定或更新的時間。
-
Originate Timestamp:NTP請求報文離開發送端時發送端的本地時間。
-
Receive Timestamp:NTP請求報文到達接收端時接收端的本地時間。
-
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; //系統時鍾的層數,取值范圍為1~16,它定義了時鍾的准確度
byte Poll = 1; //輪詢時間,即兩個連續NTP報文之間的時間間隔
byte Precision = 1; //系統時鍾的精度
BigEndianUInt32 rootDelay;
BigEndianUInt32 referenceIdentifier;
BigEndianUInt32 ReferenceIdentifier;
public NtpTime ReferenceTimestamp { get; private set; }
public NtpTime OriginateTimestamp { get; private set; }
public NtpTime ReceiveTimestamp { get; private set; }
public NtpTime TransmitTimestamp { get; private 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)
{
/* rfc1305的ntp時間中,時間是用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");
}
}
當然,我這里只是在造重復輪子,網上是有不少功能完整的開源項目的。另外,如果對SNTPv4(RFC 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服務器地址)
