Time Protocol(RFC-868)是一种非常简单的应用层协议:它返回一个32位的二进制数字,这个数字描述了从1900年1月1日0时0分0秒到现在的秒数,服务器在TCP的37号端口监听时间协议请求。本函数将服务器返回值转化成本地时间。

先前不知道有现成的IPAddress.NetworkToHostOrder函数,所以自己直接写了个ReverseBytes函数,把字节数组从Big-endian转换为Little-endian。这个函数可能在其他地方也有用,所以索性就留着了。

 

 1         private const int BUFSIZE = 4;      //字符数组的大小
 2         private const int PORT = 37;        //服务器端口号
 3         private const int TIMEOUT = 3000;   //超时时间(毫秒)
 4         private const int MAXTRIES = 3;     //尝试接受数据的次数
 5 
 6         /// <summary>
 7         /// 从NIST Internet Time Servers获取准确时间。
 8         /// </summary>
 9         /// <param name="dateTime">返回准确的本地时间</param>
10         /// <param name="timeServer">服务器列表</param>
11         /// <returns>获取时间失败将返回false,否则返回true</returns>
12         public static bool GetDateTimeFromTimeServer(out DateTime now, string timeServers = "time.nist.gov")
13         {
14             Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
15             //设置获取超时时间
16             socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, TIMEOUT);
17 
18             byte[] rcvBytes = new byte[BUFSIZE];    //接收数据的字节数组
19             int tries = 0;                          //记录尝试次数
20             bool received = false;                  //接收是否成功
21             int totalBytesRcvd = 0;                 //总共接收的字节数
22             int bytesRcvd = 0;                      //本次接收的字节数
23             do
24             {
25                 try
26                 {
27                     socket.Connect(Dns.GetHostEntry(timeServers).AddressList, PORT);
28                     while ((bytesRcvd = socket.Receive(rcvBytes, totalBytesRcvd, BUFSIZE - totalBytesRcvd, SocketFlags.None)) > 0)
29                     {
30                         totalBytesRcvd += bytesRcvd;
31                     }
32                     received = true;
33                 }
34                 catch (SocketException)
35                 {
36                     //超时或者其他Socket错误,增加参数次数
37                     tries++;
38                 }
39             } while ((!received) && (tries < MAXTRIES));
40             socket.Close();
41 
42             if (received)
43             {
44                 //将字节数组从Big-endian转换为Little-endian
45                 //ReverseBytes(ref rcvBytes, 0, 4);
46                 //UInt32 seconds = BitConverter.ToUInt32(rcvBytes, 0);
47                 UInt32 seconds = BitConverter.ToUInt32(rcvBytes, 0);
48                 if (BitConverter.IsLittleEndian)
49                 {
50                     seconds = (UInt32)IPAddress.NetworkToHostOrder((int)seconds);
51                 }
52                 //从1900年1月1日0时0分0秒日期加上获取的秒数并转换到当前本地时区时间
53                 now = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(seconds).ToLocalTime();
54                 return true;
55             }
56             else
57             {
58                 now = DateTime.Now;
59                 return false;
60             }
61         }
62 
63         /// <summary>
64         /// 翻转byte数组的字节顺序
65         /// </summary>
66         /// <param name="bytes">要翻转的字节数组</param>
67         /// <param name="start">规定转换起始位置</param>
68         /// <param name="len">要翻转的长度</param>
69         private static void ReverseBytes(ref byte[] bytes, int start, int len)
70         {
71             if ((start < 0) || (start > bytes.Length - 1) || (len > bytes.Length))
72             {
73                 throw new ArgumentOutOfRangeException();
74             }
75 
76             int end = start + len - 1;
77             if (end > bytes.Length)
78             {
79                 throw new ArgumentOutOfRangeException();
80             }
81 
82             byte tmp;
83             for (int i = 0, index = start; index < start + len / 2; index++, i++)
84             {
85                 tmp = bytes[end - i];
86                 bytes[end - i] = bytes[index];
87                 bytes[index] = tmp;
88             }
89         }

代码未经过严格测试,如果有什么错误,欢迎指出,谢谢!

 

参考文献

[1]陈香凝,王烨阳,陈婷婷,张铮.Windows网络与通信程序设计第三版[M].人民邮电出版社,2017:27-28.

[2]D.Makofske,M.Donahoo,K.Calvert.TCPIP Sockets in C# Practical Guide for Programmers[M].Morgan Kaufmann.2004。

[3]NIST Internet Time Servers.http://tf.nist.gov/tf-cgi/servers.cgi.