SNTPTimeClient.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Runtime.InteropServices;
  5. namespace Muchinfo.MTPClient.Infrastructure.Helpers
  6. {
  7. // Leap indicator field values
  8. public enum _LeapIndicator
  9. {
  10. NoWarning, // 0 - No warning
  11. LastMinute61, // 1 - Last minute has 61 seconds
  12. LastMinute59, // 2 - Last minute has 59 seconds
  13. Alarm // 3 - Alarm condition (clock not synchronized)
  14. }
  15. //Mode field values
  16. public enum _Mode
  17. {
  18. SymmetricActive, // 1 - Symmetric active
  19. SymmetricPassive, // 2 - Symmetric pasive
  20. Client, // 3 - Client
  21. Server, // 4 - Server
  22. Broadcast, // 5 - Broadcast
  23. Unknown // 0, 6, 7 - Reserved
  24. }
  25. // Stratum field values
  26. public enum _Stratum
  27. {
  28. Unspecified, // 0 - unspecified or unavailable
  29. PrimaryReference, // 1 - primary reference (e.g. radio-clock)
  30. SecondaryReference, // 2-15 - secondary reference (via NTP or SNTP)
  31. Reserved // 16-255 - reserved
  32. }
  33. [StructLayout(LayoutKind.Sequential)]
  34. public struct SystemTime
  35. {
  36. public ushort wYear;
  37. public ushort wMonth;
  38. public ushort wDayOfWeek;
  39. public ushort wDay;
  40. public ushort wHour;
  41. public ushort wMinute;
  42. public ushort wSecond;
  43. public ushort wMiliseconds;
  44. }
  45. /// <summary>
  46. /// SNTPTimeClient 的摘要说明。
  47. ///
  48. /// Public class members:
  49. ///
  50. /// LeapIndicator - Warns of an impending leap second to be inserted/deleted in the last
  51. /// minute of the current day. (See the _LeapIndicator enum)
  52. ///
  53. /// VersionNumber - Version number of the protocol (3 or 4).
  54. ///
  55. /// Mode - Returns mode. (See the _Mode enum)
  56. ///
  57. /// Stratum - Stratum of the clock. (See the _Stratum enum)
  58. ///
  59. /// PollInterval - Maximum interval between successive messages.
  60. ///
  61. /// Precision - Precision of the clock.
  62. ///
  63. /// RootDelay - Round trip time to the primary reference source.
  64. ///
  65. /// RootDispersion - Nominal error relative to the primary reference source.
  66. ///
  67. /// ReferenceID - Reference identifier (either a 4 character string or an IP address).
  68. ///
  69. /// ReferenceTimestamp - The time at which the clock was last set or corrected.
  70. ///
  71. /// OriginateTimestamp - The time at which the request departed the client for the server.
  72. ///
  73. /// ReceiveTimestamp - The time at which the request arrived at the server.
  74. ///
  75. /// Transmit Timestamp - The time at which the reply departed the server for client.
  76. ///
  77. /// RoundTripDelay - The time between the departure of request and arrival of reply.
  78. ///
  79. /// LocalClockOffset - The offset of the local clock relative to the primary reference
  80. /// source.
  81. ///
  82. /// Initialize - Sets up data structure and prepares for connection.
  83. ///
  84. /// Connect - Connects to the time server and populates the data structure.
  85. ///
  86. /// IsResponseValid - Returns true if received data is valid and if comes from
  87. /// a NTP-compliant time server.
  88. ///
  89. /// ToString - Returns a string representation of the object.
  90. ///
  91. /// -----------------------------------------------------------------------------
  92. /// Structure of the standard NTP header (as described in RFC 2030)
  93. /// 1 2 3
  94. /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  95. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  96. /// |LI | VN |Mode | Stratum | Poll | Precision |
  97. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  98. /// | Root Delay |
  99. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  100. /// | Root Dispersion |
  101. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  102. /// | Reference Identifier |
  103. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  104. /// | |
  105. /// | Reference Timestamp (64) |
  106. /// | |
  107. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  108. /// | |
  109. /// | Originate Timestamp (64) |
  110. /// | |
  111. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  112. /// | |
  113. /// | Receive Timestamp (64) |
  114. /// | |
  115. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  116. /// | |
  117. /// | Transmit Timestamp (64) |
  118. /// | |
  119. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  120. /// | Key Identifier (optional) (32) |
  121. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  122. /// | |
  123. /// | |
  124. /// | Message Digest (optional) (128) |
  125. /// | |
  126. /// | |
  127. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  128. ///
  129. /// -----------------------------------------------------------------------------
  130. ///
  131. /// NTP Timestamp Format (as described in RFC 2030)
  132. /// 1 2 3
  133. /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  134. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  135. /// | Seconds |
  136. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  137. /// | Seconds Fraction (0-padded) |
  138. /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  139. ///
  140. /// </summary>
  141. public class SNTPTimeClient
  142. {
  143. // NTP Data Structure Length
  144. private const byte NTPDataLength = 48;
  145. // NTP Data Structure (as described in RFC 2030)
  146. byte[] NTPData = new byte[NTPDataLength];
  147. // Offset constants for timestamps in the data structure
  148. private const byte offReferenceID = 12;
  149. private const byte offReferenceTimestamp = 16;
  150. private const byte offOriginateTimestamp = 24;
  151. private const byte offReceiveTimestamp = 32;
  152. private const byte offTransmitTimestamp = 40;
  153. [DllImport("Kernel32.dll")]
  154. public static extern bool SetSystemTime(ref SystemTime sysTime);
  155. [DllImport("Kernel32.dll")]
  156. public static extern bool SetLocalTime(ref SystemTime sysTime);
  157. [DllImport("Kernel32.dll")]
  158. public static extern void GetSystemTime(ref SystemTime sysTime);
  159. [DllImport("Kernel32.dll")]
  160. public static extern void GetLocalTime(ref SystemTime sysTime);
  161. // Leap Indicator
  162. public _LeapIndicator LeapIndicator
  163. {
  164. get
  165. {
  166. // Isolate the two most significant bits
  167. byte val = (byte)(NTPData[0] >> 6);
  168. switch (val)
  169. {
  170. case 0: return _LeapIndicator.NoWarning;
  171. case 1: return _LeapIndicator.LastMinute61;
  172. case 2: return _LeapIndicator.LastMinute59;
  173. case 3:
  174. default:
  175. return _LeapIndicator.Alarm;
  176. }
  177. }
  178. }
  179. // Version Number
  180. public byte VersionNumber
  181. {
  182. get
  183. {
  184. // Isolate bits 3 - 5
  185. byte val = (byte)((NTPData[0] & 0x38) >> 3);
  186. return val;
  187. }
  188. }
  189. // Mode
  190. public _Mode Mode
  191. {
  192. get
  193. {
  194. // Isolate bits 0 - 3
  195. byte val = (byte)(NTPData[0] & 0x7);
  196. switch (val)
  197. {
  198. case 0:
  199. case 6:
  200. case 7:
  201. default:
  202. return _Mode.Unknown;
  203. case 1:
  204. return _Mode.SymmetricActive;
  205. case 2:
  206. return _Mode.SymmetricPassive;
  207. case 3:
  208. return _Mode.Client;
  209. case 4:
  210. return _Mode.Server;
  211. case 5:
  212. return _Mode.Broadcast;
  213. }
  214. }
  215. }
  216. // Stratum
  217. public _Stratum Stratum
  218. {
  219. get
  220. {
  221. byte val = (byte)NTPData[1];
  222. if (val == 0) return _Stratum.Unspecified;
  223. else
  224. if (val == 1) return _Stratum.PrimaryReference;
  225. else
  226. if (val <= 15) return _Stratum.SecondaryReference;
  227. else
  228. return _Stratum.Reserved;
  229. }
  230. }
  231. // Poll Interval
  232. public uint PollInterval
  233. {
  234. get
  235. {
  236. return (uint)Math.Round(Math.Pow(2, NTPData[2]));
  237. }
  238. }
  239. // Precision (in milliseconds)
  240. public double Precision
  241. {
  242. get
  243. {
  244. return (1000 * Math.Pow(2, NTPData[3]));
  245. }
  246. }
  247. // Root Delay (in milliseconds)
  248. public double RootDelay
  249. {
  250. get
  251. {
  252. int temp = 0;
  253. temp = 256 * (256 * (256 * NTPData[4] + NTPData[5]) + NTPData[6]) + NTPData[7];
  254. return 1000 * (((double)temp) / 0x10000);
  255. }
  256. }
  257. // Root Dispersion (in milliseconds)
  258. public double RootDispersion
  259. {
  260. get
  261. {
  262. int temp = 0;
  263. temp = 256 * (256 * (256 * NTPData[8] + NTPData[9]) + NTPData[10]) + NTPData[11];
  264. return 1000 * (((double)temp) / 0x10000);
  265. }
  266. }
  267. // Reference Identifier
  268. public string ReferenceID
  269. {
  270. get
  271. {
  272. string val = "";
  273. switch (Stratum)
  274. {
  275. case _Stratum.Unspecified:
  276. case _Stratum.PrimaryReference:
  277. val += Convert.ToChar(NTPData[offReferenceID + 0]);
  278. val += Convert.ToChar(NTPData[offReferenceID + 1]);
  279. val += Convert.ToChar(NTPData[offReferenceID + 2]);
  280. val += Convert.ToChar(NTPData[offReferenceID + 3]);
  281. break;
  282. case _Stratum.SecondaryReference:
  283. //// switch(VersionNumber)
  284. //// {
  285. //// case 3: // Version 3, Reference ID is an IPv4 address
  286. //// string Address = NTPData[offReferenceID + 0].ToString() + "." +
  287. //// NTPData[offReferenceID + 1].ToString() + "." +
  288. //// NTPData[offReferenceID + 2].ToString() + "." +
  289. //// NTPData[offReferenceID + 3].ToString();
  290. //// try
  291. //// {
  292. //// IPAddress RefAddr = new IPAddress(Address);
  293. //// IPHostEntry Host = DNS.GetHostByAddr(RefAddr);
  294. //// val = Host.Hostname + " (" + Address + ")";
  295. //// }
  296. //// catch(Exception)
  297. //// {
  298. //// val = "N/A";
  299. //// }
  300. ////
  301. //// break;
  302. //// case 4: // Version 4, Reference ID is the timestamp of last update
  303. //// DateTime time = ComputeDate(GetMilliSeconds(offReferenceID));
  304. //// // Take care of the time zone
  305. //// long offset = TimeZone.CurrentTimeZone.GetUTCOffset(DateTime.Now);
  306. //// TimeSpan offspan = TimeSpan.FromTicks(offset);
  307. //// val = (time + offspan).ToString();
  308. //// break;
  309. //// default:
  310. //// val = "N/A";
  311. //// }
  312. break;
  313. }
  314. return val;
  315. }
  316. }
  317. // Reference Timestamp
  318. public DateTime ReferenceTimestamp
  319. {
  320. get
  321. {
  322. DateTime time = ComputeDate(GetMilliSeconds(offReferenceTimestamp));
  323. // Take care of the time zone
  324. long offset = Convert.ToInt64(TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now));
  325. TimeSpan offspan = TimeSpan.FromTicks(offset);
  326. return time + offspan;
  327. }
  328. }
  329. // Originate Timestamp
  330. public DateTime OriginateTimestamp
  331. {
  332. get
  333. {
  334. return ComputeDate(GetMilliSeconds(offOriginateTimestamp));
  335. }
  336. }
  337. // Receive Timestamp
  338. public DateTime ReceiveTimestamp
  339. {
  340. get
  341. {
  342. DateTime time = ComputeDate(GetMilliSeconds(offReceiveTimestamp));
  343. // Take care of the time zone
  344. long offset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Ticks;
  345. TimeSpan offspan = TimeSpan.FromTicks(offset);
  346. return time + offspan;
  347. }
  348. }
  349. // Transmit Timestamp
  350. public DateTime TransmitTimestamp
  351. {
  352. get
  353. {
  354. DateTime time = ComputeDate(GetMilliSeconds(offTransmitTimestamp));
  355. // Take care of the time zone
  356. long offset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Ticks;
  357. TimeSpan offspan = TimeSpan.FromTicks(offset);
  358. return time + offspan;
  359. }
  360. set
  361. {
  362. SetDate(offTransmitTimestamp, value);
  363. }
  364. }
  365. // Reception Timestamp
  366. public DateTime ReceptionTimestamp;
  367. // Round trip delay (in milliseconds)
  368. public int RoundTripDelay
  369. {
  370. get
  371. {
  372. TimeSpan span = (ReceiveTimestamp - OriginateTimestamp) + (ReceptionTimestamp - TransmitTimestamp);
  373. return (int)span.TotalMilliseconds;
  374. }
  375. }
  376. // Local clock offset (in milliseconds)
  377. public int LocalClockOffset
  378. {
  379. get
  380. {
  381. TimeSpan span = (ReceiveTimestamp - OriginateTimestamp) - (ReceptionTimestamp - TransmitTimestamp);
  382. return (int)(span.TotalMilliseconds / 2);
  383. }
  384. }
  385. // Compute date, given the number of milliseconds since January 1, 1900
  386. private DateTime ComputeDate(ulong milliseconds)
  387. {
  388. TimeSpan span = TimeSpan.FromMilliseconds((double)milliseconds);
  389. DateTime time = new DateTime(1900, 1, 1);
  390. time += span;
  391. return time;
  392. }
  393. // Compute the number of milliseconds, given the offset of a 8-byte array
  394. private ulong GetMilliSeconds(byte offset)
  395. {
  396. ulong intpart = 0, fractpart = 0;
  397. for (int i = 0; i <= 3; i++)
  398. {
  399. intpart = 256 * intpart + NTPData[offset + i];
  400. }
  401. for (int i = 4; i <= 7; i++)
  402. {
  403. fractpart = 256 * fractpart + NTPData[offset + i];
  404. }
  405. ulong milliseconds = intpart * 1000 + (fractpart * 1000) / 0x100000000L;
  406. return milliseconds;
  407. }
  408. // Compute the 8-byte array, given the date
  409. private void SetDate(byte offset, DateTime date)
  410. {
  411. ulong intpart = 0, fractpart = 0;
  412. DateTime StartOfCentury = new DateTime(1900, 1, 1, 0, 0, 0); // January 1, 1900 12:00 AM
  413. ulong milliseconds = (ulong)(date - StartOfCentury).TotalMilliseconds;
  414. intpart = milliseconds / 1000;
  415. fractpart = ((milliseconds % 1000) * 0x100000000L) / 1000;
  416. ulong temp = intpart;
  417. for (int i = 3; i >= 0; i--)
  418. {
  419. NTPData[offset + i] = (byte)(temp % 256);
  420. temp = temp / 256;
  421. }
  422. temp = fractpart;
  423. for (int i = 7; i >= 4; i--)
  424. {
  425. NTPData[offset + i] = (byte)(temp % 256);
  426. temp = temp / 256;
  427. }
  428. }
  429. // Initialize the NTPClient data
  430. private void Initialize()
  431. {
  432. // Set version number to 4 and Mode to 3 (client)
  433. NTPData[0] = 0x1B;
  434. string s2 = Convert.ToString(NTPData[0], 2); //NTPData[0].ToString("2");
  435. // Initialize all other fields with 0
  436. for (int i = 1; i < 48; i++)
  437. {
  438. NTPData[i] = 0;
  439. }
  440. // Initialize the transmit timestamp
  441. TransmitTimestamp = DateTime.Now;
  442. }
  443. // Connect to the time server
  444. public void Connect()
  445. {
  446. try
  447. {
  448. IPAddress hostadd = IPAddress.Parse(TimeServer);
  449. IPEndPoint EPhost = new IPEndPoint(hostadd, Convert.ToInt32(TimePort));
  450. UdpClient TimeSocket = new UdpClient();
  451. TimeSocket.Client.ReceiveTimeout = 3000;
  452. TimeSocket.Connect(EPhost);
  453. Initialize();
  454. TimeSocket.Send(NTPData, NTPData.Length);
  455. NTPData = TimeSocket.Receive(ref EPhost);
  456. if (!IsResponseValid())
  457. {
  458. throw new Exception("Invalid response from " + TimeServer);
  459. }
  460. ReceptionTimestamp = DateTime.Now;
  461. }
  462. catch (SocketException e)
  463. {
  464. throw new Exception(e.Message);
  465. }
  466. }
  467. // Check if the response from server is valid
  468. public bool IsResponseValid()
  469. {
  470. if (NTPData.Length < NTPDataLength || Mode != _Mode.Server)
  471. {
  472. return false;
  473. }
  474. else
  475. {
  476. return true;
  477. }
  478. }
  479. // Converts the object to string
  480. public override string ToString()
  481. {
  482. string str;
  483. str = "Leap Indicator: ";
  484. switch (LeapIndicator)
  485. {
  486. case _LeapIndicator.NoWarning:
  487. str += "No warning";
  488. break;
  489. case _LeapIndicator.LastMinute61:
  490. str += "Last minute has 61 seconds";
  491. break;
  492. case _LeapIndicator.LastMinute59:
  493. str += "Last minute has 59 seconds";
  494. break;
  495. case _LeapIndicator.Alarm:
  496. str += "Alarm Condition (clock not synchronized)";
  497. break;
  498. }
  499. str += "\r\nVersion number: " + VersionNumber.ToString() + "\r\n";
  500. str += "Mode: ";
  501. switch (Mode)
  502. {
  503. case _Mode.Unknown:
  504. str += "Unknown";
  505. break;
  506. case _Mode.SymmetricActive:
  507. str += "Symmetric Active";
  508. break;
  509. case _Mode.SymmetricPassive:
  510. str += "Symmetric Pasive";
  511. break;
  512. case _Mode.Client:
  513. str += "Client";
  514. break;
  515. case _Mode.Server:
  516. str += "Server";
  517. break;
  518. case _Mode.Broadcast:
  519. str += "Broadcast";
  520. break;
  521. }
  522. str += "\r\nStratum: ";
  523. switch (Stratum)
  524. {
  525. case _Stratum.Unspecified:
  526. case _Stratum.Reserved:
  527. str += "Unspecified";
  528. break;
  529. case _Stratum.PrimaryReference:
  530. str += "Primary Reference";
  531. break;
  532. case _Stratum.SecondaryReference:
  533. str += "Secondary Reference";
  534. break;
  535. }
  536. str += "\r\nLocal time: " + TransmitTimestamp.ToString();
  537. str += "\r\nPrecision: " + Precision.ToString() + " ms";
  538. str += "\r\nPoll Interval: " + PollInterval.ToString() + " s";
  539. str += "\r\nReference ID: " + ReferenceID.ToString();
  540. str += "\r\nRoot Dispersion: " + RootDispersion.ToString() + " ms";
  541. str += "\r\nRound Trip Delay: " + RoundTripDelay.ToString() + " ms";
  542. str += "\r\nLocal Clock Offset: " + LocalClockOffset.ToString() + " ms";
  543. str += "\r\n";
  544. return str;
  545. }
  546. // The URL of the time server we're connecting to
  547. private string TimeServer;
  548. private string TimePort;
  549. public SNTPTimeClient(string host, string port)
  550. {
  551. TimeServer = host;
  552. TimePort = port;
  553. }
  554. }
  555. /// <summary>
  556. /// SNTPTimeClient使用方法
  557. /// </summary>
  558. public class TestSample
  559. {
  560. /// <summary>
  561. /// Tests this instance.
  562. /// </summary>
  563. public void Test()
  564. {
  565. /*
  566. * 1. NTP基于UDP报文进行传输,使用的UDP端口号为123.
  567. * 2. 指定的IP可以是公网IP
  568. * 3. 也可以是内网局域网--须开户Windows Time服务
  569. */
  570. var client = new SNTPTimeClient("127.0.0.1", "123");
  571. client.Connect();
  572. var remoteDate = client.ReceiveTimestamp;
  573. var st = new SystemTime()
  574. {
  575. wDay = (ushort)remoteDate.Day,
  576. wDayOfWeek = (ushort)remoteDate.DayOfWeek,
  577. wHour = (ushort)remoteDate.Hour,
  578. wMiliseconds = (ushort)remoteDate.Millisecond,
  579. wMinute = (ushort)remoteDate.Minute,
  580. wMonth = (ushort)remoteDate.Month,
  581. wSecond = (ushort)remoteDate.Second,
  582. wYear = (ushort)remoteDate.Year
  583. };
  584. SNTPTimeClient.SetLocalTime(ref st);
  585. }
  586. }
  587. }