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 seconds = (UInt32)IPAddress.NetworkToHostOrder((int)seconds); 49 //从1900年1月1日0时0分0秒日期加上获取的秒数并转换到当前本地时区时间 50 now = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(seconds).ToLocalTime(); 51 return true; 52 } 53 else 54 { 55 now = DateTime.Now; 56 return false; 57 } 58 } 59 60 /// <summary> 61 /// 翻转byte数组的字节顺序 62 /// </summary> 63 /// <param name="bytes">要翻转的字节数组</param> 64 /// <param name="start">规定转换起始位置</param> 65 /// <param name="len">要翻转的长度</param> 66 private static void ReverseBytes(ref byte[] bytes, int start, int len) 67 { 68 if ((start < 0) || (start > bytes.Length - 1) || (len > bytes.Length)) 69 { 70 throw new ArgumentOutOfRangeException(); 71 } 72 73 int end = start + len - 1; 74 if (end > bytes.Length) 75 { 76 throw new ArgumentOutOfRangeException(); 77 } 78 79 byte tmp; 80 for (int i = 0, index = start; index < start + len / 2; index++, i++) 81 { 82 tmp = bytes[end - i]; 83 bytes[end - i] = bytes[index]; 84 bytes[index] = tmp; 85 } 86 }
代码未经过严格测试,如果有什么错误,欢迎指出,谢谢!
参考文献
[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.