【问题标题】:How to Query an NTP Server using C#?如何使用 C# 查询 NTP 服务器?
【发布时间】:2009-07-28 13:04:13
【问题描述】:

我所需要的只是一种使用 C# 查询 NTP 服务器的方法,以获取以 stringDateTime 形式返回的 NTP 服务器的日期时间。

这怎么可能以最简单的形式出现?

【问题讨论】:

    标签: c# datetime ntp


    【解决方案1】:

    由于已接受的旧答案已被删除(这是一个指向不再存在的 Google 代码搜索结果的链接),我想我可以回答这个问题以供将来参考:

    public static DateTime GetNetworkTime()
    {
        //default Windows time server
        const string ntpServer = "time.windows.com";
    
        // NTP message size - 16 bytes of the digest (RFC 2030)
        var ntpData = new byte[48];
    
        //Setting the Leap Indicator, Version Number and Mode values
        ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
    
        var addresses = Dns.GetHostEntry(ntpServer).AddressList;
    
        //The UDP port number assigned to NTP is 123
        var ipEndPoint = new IPEndPoint(addresses[0], 123);
        //NTP uses UDP
    
        using(var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
        {
            socket.Connect(ipEndPoint);
    
            //Stops code hang if NTP is blocked
            socket.ReceiveTimeout = 3000;     
    
            socket.Send(ntpData);
            socket.Receive(ntpData);
            socket.Close();
        }
    
        //Offset to get to the "Transmit Timestamp" field (time at which the reply 
        //departed the server for the client, in 64-bit timestamp format."
        const byte serverReplyTime = 40;
    
        //Get the seconds part
        ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
    
        //Get the seconds fraction
        ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
    
        //Convert From big-endian to little-endian
        intPart = SwapEndianness(intPart);
        fractPart = SwapEndianness(fractPart);
    
        var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
    
        //**UTC** time
        var networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);
    
        return networkDateTime.ToLocalTime();
    }
    
    // stackoverflow.com/a/3294698/162671
    static uint SwapEndianness(ulong x)
    {
        return (uint) (((x & 0x000000ff) << 24) +
                       ((x & 0x0000ff00) << 8) +
                       ((x & 0x00ff0000) >> 8) +
                       ((x & 0xff000000) >> 24));
    }
    

    注意:您必须添加以下命名空间

    using System.Net;
    using System.Net.Sockets;
    

    【讨论】:

    • @cvocvo 为此,您可以使用DateTime.ToLocalTime()
    • 在 NTP 被阻止的情况下,此代码会挂起并且永远不会返回。如何添加超时或其他内容以确保此代码返回?
    • 这是为数不多的足以直接从 Internet 剪切并粘贴到生产代码中的代码之一(当然是在测试和审查之后)。
    • 为什么我得到局域网win7时间,(使用代码位置:socket.Receive(ntpData);)它会抛出异常:An existing connection was forcibly closed by the remote host。但一切都很好,如果我使用命令行net time 来获取时间。
    • 第 17 行:var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);应该是 var socket = new Socket(addresses[0].AddressFamily, SocketType.Dgram, ProtocolType.Udp);这样,如果第一个地址是 IP6,它将起作用
    【解决方案2】:

    这是该函数的优化版本,它消除了对BitConverter函数的依赖,使其与NETMF(.NET Micro Framework)兼容

    public static DateTime GetNetworkTime()
    {
        const string ntpServer = "pool.ntp.org";
        var ntpData = new byte[48];
        ntpData[0] = 0x1B; //LeapIndicator = 0 (no warning), VersionNum = 3 (IPv4 only), Mode = 3 (Client Mode)
    
        var addresses = Dns.GetHostEntry(ntpServer).AddressList;
        var ipEndPoint = new IPEndPoint(addresses[0], 123);
        var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    
        socket.Connect(ipEndPoint);
        socket.Send(ntpData);
        socket.Receive(ntpData);
        socket.Close();
    
        ulong intPart = (ulong)ntpData[40] << 24 | (ulong)ntpData[41] << 16 | (ulong)ntpData[42] << 8 | (ulong)ntpData[43];
        ulong fractPart = (ulong)ntpData[44] << 24 | (ulong)ntpData[45] << 16 | (ulong)ntpData[46] << 8 | (ulong)ntpData[47];
    
        var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
        var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);
    
        return networkDateTime;
    }
    

    【讨论】:

    • 您是否错过了超时...socket.ReceiveTimeout = 3000; ...这可以防止出现网络问题时挂起。值以毫秒为单位。
    • 注意 UTC:var networkDateTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(milliseconds); return networkDateTime.ToLocalTime();
    【解决方案3】:

    在 CodePlex 中找到的The .NET Micro Framework Toolkit 有一个NTPClient。我自己从来没有用过,但看起来不错。

    还有另一个例子位于here

    【讨论】:

    【解决方案4】:

    我知道这个话题已经很老了,但这样的工具总是很方便。我使用了上面的资源并创建了一个 NtpClient 版本,它允许异步获取准确的时间,而不是基于事件。

     /// <summary>
    /// Represents a client which can obtain accurate time via NTP protocol.
    /// </summary>
    public class NtpClient
    {
        private readonly TaskCompletionSource<DateTime> _resultCompletionSource;
    
        /// <summary>
        /// Creates a new instance of <see cref="NtpClient"/> class.
        /// </summary>
        public NtpClient()
        {
            _resultCompletionSource = new TaskCompletionSource<DateTime>();
        }
    
        /// <summary>
        /// Gets accurate time using the NTP protocol with default timeout of 45 seconds.
        /// </summary>
        /// <returns>Network accurate <see cref="DateTime"/> value.</returns>
        public async Task<DateTime> GetNetworkTimeAsync()
        {
            return await GetNetworkTimeAsync(TimeSpan.FromSeconds(45));
        }
    
        /// <summary>
        /// Gets accurate time using the NTP protocol with default timeout of 45 seconds.
        /// </summary>
        /// <param name="timeoutMs">Operation timeout in milliseconds.</param>
        /// <returns>Network accurate <see cref="DateTime"/> value.</returns>
        public async Task<DateTime> GetNetworkTimeAsync(int timeoutMs)
        {
            return await GetNetworkTimeAsync(TimeSpan.FromMilliseconds(timeoutMs));
        }
    
        /// <summary>
        /// Gets accurate time using the NTP protocol with default timeout of 45 seconds.
        /// </summary>
        /// <param name="timeout">Operation timeout.</param>
        /// <returns>Network accurate <see cref="DateTime"/> value.</returns>
        public async Task<DateTime> GetNetworkTimeAsync(TimeSpan timeout)
        {
            using (var socket = new DatagramSocket())
            using (var ct = new CancellationTokenSource(timeout))
            {
                ct.Token.Register(() => _resultCompletionSource.TrySetCanceled());
    
                socket.MessageReceived += OnSocketMessageReceived;
                //The UDP port number assigned to NTP is 123
                await socket.ConnectAsync(new HostName("pool.ntp.org"), "123");
                using (var writer = new DataWriter(socket.OutputStream))
                {
                    // NTP message size is 16 bytes of the digest (RFC 2030)
                    var ntpBuffer = new byte[48];
    
                    // Setting the Leap Indicator, 
                    // Version Number and Mode values
                    // LI = 0 (no warning)
                    // VN = 3 (IPv4 only)
                    // Mode = 3 (Client Mode)
                    ntpBuffer[0] = 0x1B;
    
                    writer.WriteBytes(ntpBuffer);
                    await writer.StoreAsync();
                    var result = await _resultCompletionSource.Task;
                    return result;
                }
            }
        }
    
        private void OnSocketMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
        {
            try
            {
                using (var reader = args.GetDataReader())
                {
                    byte[] response = new byte[48];
                    reader.ReadBytes(response);
                    _resultCompletionSource.TrySetResult(ParseNetworkTime(response));
                }
            }
            catch (Exception ex)
            {
                _resultCompletionSource.TrySetException(ex);
            }
        }
    
        private static DateTime ParseNetworkTime(byte[] rawData)
        {
            //Offset to get to the "Transmit Timestamp" field (time at which the reply 
            //departed the server for the client, in 64-bit timestamp format."
            const byte serverReplyTime = 40;
    
            //Get the seconds part
            ulong intPart = BitConverter.ToUInt32(rawData, serverReplyTime);
    
            //Get the seconds fraction
            ulong fractPart = BitConverter.ToUInt32(rawData, serverReplyTime + 4);
    
            //Convert From big-endian to little-endian
            intPart = SwapEndianness(intPart);
            fractPart = SwapEndianness(fractPart);
    
            var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
    
            //**UTC** time
            DateTime networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);
            return networkDateTime;
        }
    
        // stackoverflow.com/a/3294698/162671
        private static uint SwapEndianness(ulong x)
        {
            return (uint)(((x & 0x000000ff) << 24) +
                           ((x & 0x0000ff00) << 8) +
                           ((x & 0x00ff0000) >> 8) +
                           ((x & 0xff000000) >> 24));
        }
    }
    

    用法:

    var ntp = new NtpClient();
    var accurateTime = await ntp.GetNetworkTimeAsync(TimeSpan.FromSeconds(10));
    

    【讨论】:

    • 不适用于 Windows 7,仅适用于 Windows 10。请参阅 windows.networking.sockets.datagramsocket
    • 我已经在 Raspberry Pi 3 中使用了这个 inWind 10 IoT Core 项目,考虑到当 pi 关闭时时间停止,我可以很好地工作。 (没有实时时钟)
    【解决方案5】:

    补偿网络时间并使用 DateTime-Ticks 计算的修改版本(比毫秒更精确)

    public static DateTime GetNetworkTime()
    {
      const string NtpServer = "pool.ntp.org";
    
      const int DaysTo1900 = 1900 * 365 + 95; // 95 = offset for leap-years etc.
      const long TicksPerSecond = 10000000L;
      const long TicksPerDay = 24 * 60 * 60 * TicksPerSecond;
      const long TicksTo1900 = DaysTo1900 * TicksPerDay;
    
      var ntpData = new byte[48];
      ntpData[0] = 0x1B; // LeapIndicator = 0 (no warning), VersionNum = 3 (IPv4 only), Mode = 3 (Client Mode)
    
      var addresses = Dns.GetHostEntry(NtpServer).AddressList;
      var ipEndPoint = new IPEndPoint(addresses[0], 123);
      long pingDuration = Stopwatch.GetTimestamp(); // temp access (JIT-Compiler need some time at first call)
      using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
      {
        socket.Connect(ipEndPoint);
        socket.ReceiveTimeout = 5000;
        socket.Send(ntpData);
        pingDuration = Stopwatch.GetTimestamp(); // after Send-Method to reduce WinSocket API-Call time
    
        socket.Receive(ntpData);
        pingDuration = Stopwatch.GetTimestamp() - pingDuration;
      }
    
      long pingTicks = pingDuration * TicksPerSecond / Stopwatch.Frequency;
    
      // optional: display response-time
      // Console.WriteLine("{0:N2} ms", new TimeSpan(pingTicks).TotalMilliseconds);
    
      long intPart = (long)ntpData[40] << 24 | (long)ntpData[41] << 16 | (long)ntpData[42] << 8 | ntpData[43];
      long fractPart = (long)ntpData[44] << 24 | (long)ntpData[45] << 16 | (long)ntpData[46] << 8 | ntpData[47];
      long netTicks = intPart * TicksPerSecond + (fractPart * TicksPerSecond >> 32);
    
      var networkDateTime = new DateTime(TicksTo1900 + netTicks + pingTicks / 2);
    
      return networkDateTime.ToLocalTime(); // without ToLocalTime() = faster
    }
    

    【讨论】:

    • 我建议更改行 var networkDateTime = new DateTime(TicksTo1900 + netTicks + pingTicks / 2); by var networkDateTime = new DateTime(ticksTo1900 + netTicks + pingTicks / 2, DateTimeKind.Utc);
    • 您好,贝斯特,感谢您的提示。对于 .ToLocalTime() UTC-Kind 不是强制性的。如果您需要带有可选 UTC 标记的时间戳,您也可以在返回时调用 .ToUniversalTime()。原因:我使用了没有 DateTimeKind 的构造函数,因为这比处理器快 2-3 个滴答声。 :D
    【解决方案6】:

    http://www.codeproject.com/Articles/237501/Windows-Phone-NTP-Client 适用于 Windows Phone。

    添加相关代码

    /// <summary>
    /// Class for acquiring time via Ntp. Useful for applications in which correct world time must be used and the 
    /// clock on the device isn't "trusted."
    /// </summary>
    public class NtpClient
    {
        /// <summary>
        /// Contains the time returned from the Ntp request
        /// </summary>
        public class TimeReceivedEventArgs : EventArgs
        {
            public DateTime CurrentTime { get; internal set; }
        }
    
        /// <summary>
        /// Subscribe to this event to receive the time acquired by the NTP requests
        /// </summary>
        public event EventHandler<TimeReceivedEventArgs> TimeReceived;
    
        protected void OnTimeReceived(DateTime time)
        {
            if (TimeReceived != null)
            {
                TimeReceived(this, new TimeReceivedEventArgs() { CurrentTime = time });
            }
        }
    
    
        /// <summary>
        /// Not reallu used. I put this here so that I had a list of other NTP servers that could be used. I'll integrate this
        /// information later and will provide method to allow some one to choose an NTP server.
        /// </summary>
        public string[] NtpServerList = new string[]
        {
            "pool.ntp.org ",
            "asia.pool.ntp.org",
            "europe.pool.ntp.org",
            "north-america.pool.ntp.org",
            "oceania.pool.ntp.org",
            "south-america.pool.ntp.org",
            "time-a.nist.gov"
        };
    
        string _serverName;
        private Socket _socket;
    
        /// <summary>
        /// Constructor allowing an NTP server to be specified
        /// </summary>
        /// <param name="serverName">the name of the NTP server to be used</param>
        public NtpClient(string serverName)
        {
            _serverName = serverName;
        }
    
    
        /// <summary>
        /// 
        /// </summary>
        public NtpClient()
            : this("time-a.nist.gov")
        { }
    
        /// <summary>
        /// Begins the network communication required to retrieve the time from the NTP server
        /// </summary>
        public void RequestTime()
        {
            byte[] buffer = new byte[48];
            buffer[0] = 0x1B;
            for (var i = 1; i < buffer.Length; ++i)
                buffer[i] = 0;
            DnsEndPoint _endPoint = new DnsEndPoint(_serverName, 123);
    
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            SocketAsyncEventArgs sArgsConnect = new SocketAsyncEventArgs() { RemoteEndPoint = _endPoint };
            sArgsConnect.Completed += (o, e) =>
            {
                if (e.SocketError == SocketError.Success)
                {
                    SocketAsyncEventArgs sArgs = new SocketAsyncEventArgs() { RemoteEndPoint = _endPoint };
                    sArgs.Completed +=
                        new EventHandler<SocketAsyncEventArgs>(sArgs_Completed);
                    sArgs.SetBuffer(buffer, 0, buffer.Length);
                    sArgs.UserToken = buffer;
                    _socket.SendAsync(sArgs);
                }
            };
            _socket.ConnectAsync(sArgsConnect);
    
        }
    
        void sArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                byte[] buffer = (byte[])e.Buffer;
                SocketAsyncEventArgs sArgs = new SocketAsyncEventArgs();
                sArgs.RemoteEndPoint = e.RemoteEndPoint;
    
                sArgs.SetBuffer(buffer, 0, buffer.Length);
                sArgs.Completed += (o, a) =>
                {
                    if (a.SocketError == SocketError.Success)
                    {
                        byte[] timeData = a.Buffer;
    
                        ulong hTime = 0;
                        ulong lTime = 0;
    
                        for (var i = 40; i <= 43; ++i)
                            hTime = hTime << 8 | buffer[i];
                        for (var i = 44; i <= 47; ++i)
                            lTime = lTime << 8 | buffer[i];
                        ulong milliseconds = (hTime * 1000 + (lTime * 1000) / 0x100000000L);
    
                        TimeSpan timeSpan =
                            TimeSpan.FromTicks((long)milliseconds * TimeSpan.TicksPerMillisecond);
                        var currentTime = new DateTime(1900, 1, 1) + timeSpan;
                        OnTimeReceived(currentTime);
    
                    }
                };
                _socket.ReceiveAsync(sArgs);
            }
        }
    }
    

    用法:

    public partial class MainPage : PhoneApplicationPage
    {
        private NtpClient _ntpClient;
        public MainPage()
        {
            InitializeComponent();
            _ntpClient = new NtpClient();
            _ntpClient.TimeReceived += new EventHandler<NtpClient.TimeReceivedEventArgs>(_ntpClient_TimeReceived);
        }
    
        void _ntpClient_TimeReceived(object sender, NtpClient.TimeReceivedEventArgs e)
        {
            this.Dispatcher.BeginInvoke(() =>
                                            {
                                                txtCurrentTime.Text = e.CurrentTime.ToLongTimeString();
                                                txtSystemTime.Text = DateTime.Now.ToUniversalTime().ToLongTimeString();
                                            });
        }
    
        private void UpdateTimeButton_Click(object sender, RoutedEventArgs e)
        {
            _ntpClient.RequestTime();
        }
    }
    

    【讨论】:

    • SocketIDisposable。您应该考虑到这一点来设计您的类,并提供一种在正常使用和引发异常时释放套接字的方法。此代码确实会导致内存泄漏
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多