查看: 704|回复: 0

[.NET源码] 根据Time Protocol从NIST Internet Time Servers获取准确时间

发表于 2017-9-25 08:00:03
尚学堂AD

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

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

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



回复

使用道具 举报