วิธีการสอบถามเซิร์ฟเวอร์ NTP โดยใช้ C #


106

สิ่งที่ฉันต้องการคือวิธีสืบค้นเซิร์ฟเวอร์ NTP โดยใช้ C # เพื่อรับวันที่และเวลาของเซิร์ฟเวอร์ NTP ที่ส่งคืนเป็น a stringหรือเป็นไฟล์DateTime.

เป็นไปได้อย่างไรในรูปแบบที่ง่ายที่สุด?

คำตอบ:


156

เนื่องจากคำตอบที่ยอมรับเดิมถูกลบไป (เป็นลิงก์ไปยังผลการค้นหารหัสของ 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;

2
@cvocvo เพื่อที่คุณสามารถใช้ได้DateTime.ToLocalTime()
Nasreddine

2
ภายใต้สถานการณ์ที่ NTP ถูกบล็อกรหัสนี้จะแฮงค์และไม่ส่งคืน ฉันจะเพิ่มระยะหมดเวลาหรือบางสิ่งเพื่อให้แน่ใจว่ารหัสนี้ส่งคืนได้อย่างไร
cvocvo

4
นี่เป็นหนึ่งในโค้ดไม่กี่ชิ้นที่ดีพอที่จะตัดและวางโดยตรงจากอินเทอร์เน็ตเป็นรหัสการผลิต (หลังจากการทดสอบและตรวจสอบแน่นอน)
dodgy_coder

2
ทำไมฉันถึงได้ LAN win7 time (ใช้ code posision :) socket.Receive(ntpData);มันจะทำให้เกิดข้อยกเว้น: An existing connection was forcibly closed by the remote host. แต่ทั้งหมดก็ดีถ้าฉันใช้บรรทัดคำสั่งnet timeเพื่อรับเวลา
qakmak

1
บรรทัดที่ 17: var socket = ซ็อกเก็ตใหม่ (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); ควรเป็น var socket = new Socket (address [0] .AddressFamily, SocketType.Dgram, ProtocolType.Udp); วิธีนี้จะได้ผลหากที่อยู่ที่ 1 ถ้า IP6
Duane McKinney

34

ฟังก์ชันนี้เป็นเวอร์ชันที่ปรับให้เหมาะสมซึ่งจะลบการพึ่งพาฟังก์ชัน 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;
}

7
คุณพลาดการหมดเวลา ... socket.ReceiveTimeout = 3000;... ซึ่งจะป้องกันไม่ให้แฮงค์หากมีปัญหาเครือข่าย ค่าเป็นมิลลิวินาที
dodgy_coder

3
ดูแล UTC: var networkDateTime = DateTime ใหม่ (1900, 1, 1, 0, 0, 0, DateTimeKind.Utc) .AddMilliseconds (มิลลิวินาที); ส่งคืน networkDateTime ToLocalTime ();
Daniel Fisher lennybacon


6

ฉันรู้ว่าหัวข้อนี้ค่อนข้างเก่า แต่เครื่องมือดังกล่าวมักมีประโยชน์เสมอ ฉันได้ใช้ทรัพยากรข้างต้นและสร้างเวอร์ชันของ 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));

1
ไม่ทำงานใน Windows 7 เฉพาะใน Windows 10 ดูwindows.networking.sockets.datagramsocket
stomy

ฉันใช้โครงการ inWind 10 IoT Core กับ Raspberry Pi 3 แล้วทำงานได้ดีเมื่อเวลาหยุดเมื่อปิด pi (ไม่มีนาฬิกาตามเวลาจริง)
Chris Schaller

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 = DateTime ใหม่ (TicksTo1900 + netTicks + pingTicks / 2); โดย var networkDateTime = DateTime ใหม่ (ticksTo1900 + netTicks + pingTicks / 2, DateTimeKind.Utc);
Bestter

สวัสดี Besster ขอบคุณสำหรับคำใบ้ สำหรับ. ToLocalTime () UTC-Kind ไม่ได้บังคับ หากคุณต้องการการประทับเวลาที่มีเครื่องหมาย UTC ซึ่งเป็นทางเลือกคุณสามารถเรียก. ToUniversalTime () เมื่อส่งคืน เหตุผล: ฉันใช้ตัวสร้างที่ไม่มี DateTimeKind เนื่องจากตัวประมวลผล 2-3 ตัวนี้เร็วกว่า : D
MaxKlaxx

-1

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();
    }
}

SocketคือIDisposable. คุณควรออกแบบคลาสของคุณโดยคำนึงถึงสิ่งนั้นและจัดเตรียมวิธีการปลดซ็อกเก็ตทั้งในการใช้งานปกติและเมื่อใดก็ตามที่มีข้อยกเว้นเพิ่มขึ้น รหัสนี้ทำให้หน่วยความจำรั่ว
gfache
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.