Time Protocol(RFC-868)是一種非常簡單的應用層協議:它返回一個32位的二進位數字,這個數字描述了從1900年1月1日0時0分0秒到現在的秒數,伺服器在TCP的37號埠監聽時間協議請求。本函數將伺服器返回值轉化成本地時間。 先前不知道有現成的IPAddress.NetworkTo ...
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.