zoukankan      html  css  js  c++  java
  • 获取安全时间

        “安全时间”的意义大多在于信息安全上,也可以为用户提供更加准确的时间服务。本文主要探讨如何利用网络时间协议 (Network Time Protocol, NTP)来进行网络授时 (Time signal)。NTP 可以适应网络的延迟,从而最大化的保证用户获取到的时间的准确性。其同步算法和原理参见这里 (Clock synchronization algorithm)

        在写本文之前,原本想使用国家授时中心提供的时间数据。但由于本人愚钝,没能在官方网站找到相关的授时接口,所以采用了全球通用的 NTP 来进行网络授时。

        获取时间:根据 RFC 2030(已过时,最新为 RFC 5905),NTP 可以利用 UDP 协议工作在 IPv4IPv6 上。通过向 NTP 服务器发送一个请求,服务器给你返回一个时间响应。数据包格式在 RFC 2030 的“4. NTP Message Format”章节进行了定义。

        在“5. SNTP Client Operations”章节说明了用户如何向服务器发送请求。因为“A unicast or anycast client initializes the NTP message header, sends the request to the server and strips the time of day from the Transmit Timestamp field of the reply. For this purpose, all of the NTP header fields shown above can be set to 0, except the first octet and (optional) Transmit Timestamp fields. In the first octet, the LI field is set to 0 (no warning) and the Mode field is set to 3 (client). The VN field must agree with the version number of the NTP/SNTP server”,所以我们可以简单的把第一个字节的数据置为 0x1b (0001 1011),即:LI=0, VN=4, Mode=3,其他数据一律置零。

        接下来服务器会返回相关报文。RFC 2030 给出的时间计算公式:d = (T4 - T1) - (T2 - T3)     t = ((T2 - T1) + (T3 - T4)) / 2,最终我们可以得到相对比较精确的本地时间。简化后的公式近似为(服务器的timespan+本机等待时间/2)。

        跳动时间:通过一个线程,每隔一个安全周期主动向服务器获取一次时间;或者在本机发现时间出现过大跳动的时候也向服务器获取一次时间。在获取时间时如果发生了异常,从列表中挑选下一个地址作为服务器地址。

        实现代码

        public static class SafeDateTime
        {
            const int SAFE_CYCLE = 7200;
            const int CYCLE_INTERVAL = 500;
            const int ALLOW_DIFF = 650;
    
            readonly static string[] _hosts = new[] { // list of NTP servers
                "ntp.sjtu.edu.cn", // 上海交通大学
                "time-nw.nist.gov", // Microsoft, Redmond, Washington 
                "s1a.time.edu.cn", // 北京邮电大学
                "time-b.timefreq.bldrdoc.gov", // NIST, Boulder, Colorado 
                "133.100.11.8", // 日本 福冈大学
            };
            readonly static IPEndPoint[] _eps = null;
    
            static int _sIndex = 0;
            static int _safeCycle = 0;
            static bool _onGetingTime = false;
            static DateTime _localUtcTime;
            static DateTime _networkUtcTime;
    
            static SafeDateTime()
            {
                // convert hosts to IPEndPoints
                //_eps = _hosts.Select(s => (IEnumerable<IPAddress>)Dns.GetHostAddresses(s)).Aggregate((x, y) => x.Concat(y)).Select(s => new IPEndPoint(s, 123)).ToArray();
                var list = new List<IPEndPoint>();
                foreach (var host in _hosts)
                {
                    try
                    {
                        foreach (var ip in Dns.GetHostAddresses(host))
                            list.Add(new IPEndPoint(ip, 123));
                    }
                    catch { }
                }
                _eps = list.ToArray();
    
                new Thread(() =>
                {
                    var currentThread = Thread.CurrentThread;
                    currentThread.Priority = ThreadPriority.Highest;
                    currentThread.IsBackground = true;
    
                    DateTime lastSafeTime = DateTime.MinValue;
                    DateTime currentSafeTime = DateTime.MinValue;
    
                    while (true)
                    {
                        if (_safeCycle-- <= 0) // expire the safe times
                        {
                            AsyncNetworkUtcTime();
                            _safeCycle = SAFE_CYCLE;
                        }
                        else
                        {
                            currentSafeTime = GetSafeDateTime();
                            var diff = (currentSafeTime - lastSafeTime).Ticks;
                            if (Math.Abs(diff) > ALLOW_DIFF) // out of threshold
                                AsyncNetworkUtcTime();
                        }
    
                        lastSafeTime = GetSafeDateTime();
                        Thread.Sleep(CYCLE_INTERVAL);
                    }
                }).Start();
            }
    
            private static DateTime GetNetworkUtcTime(IPEndPoint ep)
            {
                Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                s.Connect(ep);
    
                byte[] ntpData = new byte[48]; // RFC 2030 
                ntpData[0] = 0x1B;
                //for (int i = 1; i < 48; i++)
                //    ntpData[i] = 0;
    
                // t1, time request sent by client
                // t2, time request received by server
                // t3, time reply sent by server
                // t4, time reply received by client
                long t1, t2, t3, t4;
    
                t1 = DateTime.UtcNow.Ticks;
                s.Send(ntpData);
                s.Receive(ntpData);
                t4 = DateTime.UtcNow.Ticks;
                s.Close();
    
                t2 = ParseRaw(ntpData, 32);
                t3 = ParseRaw(ntpData, 40);
    
                long d = (t4 - t1) - (t3 - t2); // roundtrip delay
                long ticks = (t3 + (d >> 1));
    
                var timeSpan = TimeSpan.FromTicks(ticks);
                var dateTime = new DateTime(1900, 1, 1) + timeSpan;
    
                return dateTime; // return Utc time
            }
    
            private static long ParseRaw(byte[] ntpData, int offsetTransmitTime)
            {
                ulong intpart = 0;
                ulong fractpart = 0;
    
                for (int i = 0; i <= 3; i++)
                    intpart = (intpart << 8) | ntpData[offsetTransmitTime + i];
    
                for (int i = 4; i <= 7; i++)
                    fractpart = (fractpart << 8) | ntpData[offsetTransmitTime + i];
    
                ulong milliseconds = (intpart * 1000 + (fractpart * 1000) / 0x100000000L);
    
                return (long)milliseconds * TimeSpan.TicksPerMillisecond;
            }
    
            private static void AsyncNetworkUtcTime()
            {
                if (_onGetingTime) // simple to avoid thread conflict
                    return;
                _onGetingTime = true;
    
                bool fail = true;
                do
                {
                    try
                    {
                        _networkUtcTime = GetNetworkUtcTime(_eps[_sIndex]);
                        _localUtcTime = DateTime.UtcNow;
                        fail = false;
                    }
                    catch
                    {
                        _sIndex = (_sIndex + 1) % _eps.Length;
                    }
                } while (fail);
    
                _onGetingTime = false;
            }
    
            public static DateTime GetSafeDateTime()
            {
                var utcNow = DateTime.UtcNow;
                var interval = utcNow - _localUtcTime;
                return (_networkUtcTime + interval).ToLocalTime();
            }
        }
    

        线程安全:在更新 _networkUtcTime 和 _localUtcTime 的时候可能会导致线程不安全,但是发生冲突概率极低。可以根据情况适当的增加线程锁。

    PS:未尽事宜,请自行阅读源代码和 RFC 文档进行理解。

  • 相关阅读:
    sharepoint_study_10
    sharepoint_study_9
    sharepoint_study_8
    需要经常读的文章(长期更新)
    sharepoint_study_7
    sharepoint_study_目录学习笔记(长期更新)
    windows_study_2
    sharepoint_study_6
    sharepoint_study_5
    sharepoint_study_4
  • 原文地址:https://www.cnblogs.com/Aimeast/p/2558404.html
Copyright © 2011-2022 走看看