首页 > 编程语言 >dotnet6 C# 一个国内还能用的 NTP 时间校准客户端的实现

dotnet6 C# 一个国内还能用的 NTP 时间校准客户端的实现

时间:2023-09-05 15:47:04浏览次数:49  
标签:dateTimeOffset await C# NTP dotnet6 时间 var com

本文来记录一个我自己在使用的 NTP 时间校准客户端的实现

核心方法是在国内使用 腾讯 和 阿里 提供的 NTP 时间服务器来获取网络时间,如果连接不上,再依次换成 国家服务器 和 中国授时 服务,如果再连不上,那就换成微软自带的 time.windows.com 服务

从 NTP 服务上获取当前的网络时间,可采用 RFC 2030 提供的协议的方法,此方法只需要发送一条 UDP 消息和接收一条消息即可。服务器端返回的是相对于 1900.1.1 的毫秒时间

我从 https://github.com/michaelschwarz/NETMF-Toolkit/blob/095b01679945c3f518dd52082eca78bbaff9811f/NTP/NtpClient.cs 找到了核心实现方法,然后进行了一些魔改,改动核心是优化了异步

下面是修改之后的代码

// https://github.com/michaelschwarz/NETMF-Toolkit/blob/095b01679945c3f518dd52082eca78bbaff9811f/NTP/NtpClient.cs
public static class NtpClient
{
    /// <summary>
    /// 国内的授时服务提供的网络时间。默认返回北京时区的时间。如需转换为本机时区时间,请使用 <code> var dateTimeOffset = NtpClient.GetChineseNetworkTime();var 本机时区时间 = dateTimeOffset.LocalDateTime;</code> 转换。本机时区时间和北京时间的差别是,本机系统时区可能被设置为非北京时间,当本机系统时区设置为北京时间,则本机时区时间和北京时间相同
    /// </summary>
    /// <remarks>实现方法是去询问腾讯和阿里的授时服务器</remarks>
    /// <returns>返回空表示没有能够获取到任何的时间,预计是网络错误了。返回北京时区的时间</returns>
    /// 本来想着异常对外抛出的,但是似乎抛出异常也没啥用
    public static async ValueTask<DateTimeOffset?> GetChineseNetworkTime()
    {
        // 感谢 [国内外常用公共NTP网络时间同步服务器地址_味辛的博客-CSDN博客_ntp服务器](https://blog.csdn.net/weixin_42588262/article/details/82501488 )
        var dateTimeOffset = await GetChineseNetworkTimeCore("ntp.tencent.com"); // 腾讯
        dateTimeOffset ??= await GetChineseNetworkTimeCore("ntp.aliyun.com"); // 阿里
        dateTimeOffset ??= await GetChineseNetworkTimeCore("cn.pool.ntp.org"); // 国家服务器
        dateTimeOffset ??= await GetChineseNetworkTimeCore("cn.ntp.org.cn"); // 中国授时
        dateTimeOffset ??= await GetChineseNetworkTimeCore("time.windows.com"); // time.windows.com 微软Windows自带

        if (dateTimeOffset is not null)
        {
            return dateTimeOffset.Value.ToOffset(TimeSpan.FromHours(8));
        }
        else
        {
            return null;
        }

        static async ValueTask<DateTimeOffset?> GetChineseNetworkTimeCore(string ntpServer)
        {
            var cancellationTokenSource = new CancellationTokenSource();
            try
            {
                var hostEntry = await Dns.GetHostEntryAsync(ntpServer);
                IPAddress[] addressList = hostEntry.AddressList;

                if (addressList.Length == 0)
                {
                    // 被投毒了?那就换其他一个吧
                    return null;
                }

                foreach (var address in addressList)
                {
                    try
                    {
                        var ipEndPoint = new IPEndPoint(address, 123);
                        cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(15));

                        return await GetNetworkUtcTime(ipEndPoint, cancellationTokenSource.Token);
                    }
                    catch
                    {
                        // 失败就继续换下一个
                    }

                    if (!cancellationTokenSource.TryReset())
                    {
                        cancellationTokenSource.Dispose();
                        cancellationTokenSource = new CancellationTokenSource();
                    }
                }
            }
            catch
            {
                // 失败就失败
                // 本来想着异常对外抛出的,但是似乎抛出异常也没啥用
            }
            finally
            {
                cancellationTokenSource.Dispose();
            }

            return null;
        }
    }

    /// <summary>
    /// Gets the current DateTime from time-a.nist.gov.
    /// </summary>
    /// <returns>A DateTime containing the current time.</returns>
    public static ValueTask<DateTimeOffset> GetNetworkUtcTime()
    {
        return GetNetworkUtcTime("time-a.nist.gov");
    }

    /// <summary>
    /// Gets the current DateTime from <paramref name="ntpServer"/>.
    /// </summary>
    /// <param name="ntpServer">The hostname of the NTP server.</param>
    /// <returns>A DateTime containing the current time.</returns>
    public static async ValueTask<DateTimeOffset> GetNetworkUtcTime(string ntpServer)
    {
        var hostEntry = await Dns.GetHostEntryAsync(ntpServer);
        IPAddress[] address = hostEntry.AddressList;

        if (address == null || address.Length == 0)
        {
            throw new ArgumentException($"Could not resolve ip address from '{ntpServer}'.", "ntpServer");
        }

        var ipEndPoint = new IPEndPoint(address[0], 123);

        return await GetNetworkUtcTime(ipEndPoint);
    }

    /// <summary>
    /// Gets the current DateTime form <paramref name="endPoint"/> IPEndPoint.
    /// </summary>
    /// <param name="endPoint">The IPEndPoint to connect to.</param>
    /// <param name="token"></param>
    /// <returns>A DateTime containing the current time.</returns>
    public static async ValueTask<DateTimeOffset> GetNetworkUtcTime(IPEndPoint endPoint,
        CancellationToken token = default)
    {
        using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        await socket.ConnectAsync(endPoint, token);

        const int length = 48;

        // 实现方法请参阅 RFC 2030 的内容
        var ntpData = ArrayPool<byte>.Shared.Rent(length);

        try
        {
            // 初始化数据
            ntpData[0] = 0x1B;
            for (int i = 1; i < length; i++)
            {
                ntpData[i] = 0;
            }

            await socket.SendAsync(ntpData.AsMemory(0, length), token);
            await socket.ReceiveAsync(ntpData.AsMemory(0, length), token);

            byte offsetTransmitTime = 40;
            ulong intPart = 0;
            ulong fractPart = 0;

            for (int i = 0; i <= 3; i++)
            {
                intPart = 256 * intPart + ntpData[offsetTransmitTime + i];
            }

            for (int i = 4; i <= 7; i++)
            {
                fractPart = 256 * fractPart + ntpData[offsetTransmitTime + i];
            }

            ulong milliseconds = (intPart * 1000 + (fractPart * 1000) / 0x100000000L);

            TimeSpan timeSpan = TimeSpan.FromMilliseconds(milliseconds);

            var dateTime = new DateTime(1900, 1, 1);
            dateTime += timeSpan;

            var dateTimeOffset = new DateTimeOffset(dateTime, TimeSpan.Zero);

            return dateTimeOffset;
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(ntpData);
        }
    }
}

以上代码使用返回值是 DateTimeOffset 类型,此 DateTimeOffset 和 DateTime 的最大差别在于 DateTimeOffset 是带时区的。回顾一下小学知识,北京时间是 +8 小时的时间。时间服务器返回的是 UTC 时区时间,也就是 +0 小时。这就是为什么上层函数使用了 dateTimeOffset.Value.ToOffset(TimeSpan.FromHours(8)); 代码的原因,将 UTC 时区修改为北京时区

以上代码的使用方法如下

        var dateTimeOffset = await NtpClient.GetChineseNetworkTime();

        if (dateTimeOffset is null)
        {
            Console.WriteLine("获取不到时间");
        }
        else
        {
            Console.WriteLine(dateTimeOffset);
            Console.WriteLine(dateTimeOffset.Value.LocalDateTime);

            // 本机时区时间和北京时间的差别是,本机系统时区可能被设置为非北京时间,当本机系统时区设置为北京时间,则本机时区时间和北京时间相同
            DateTime beijingTime = dateTimeOffset.Value.UtcDateTime.AddHours(8);
            Console.WriteLine(beijingTime);
        }

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 0a9b16e50faad9240b07f62064bc1f498b1d6619

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 0a9b16e50faad9240b07f62064bc1f498b1d6619

获取代码之后,进入 JakairhefeHajelaycaqa 文件夹

更多博客,请参阅我的 博客导航

标签:dateTimeOffset,await,C#,NTP,dotnet6,时间,var,com
From: https://www.cnblogs.com/lindexi/p/17679815.html

相关文章

  • Go - benchmark cpuprofile
    zzh@ZZHPC:/zdata/MyPrograms/Go/aaa$gotest-runNONE-bench.goos:linuxgoarch:amd64pkg:zzh/aaacpu:Intel(R)Core(TM)i5-9600KCPU@3.70GHzBenchmarkTokenize-66016419742ns/opPASSokzzh/aaa1.392szzh@ZZHPC:/zdata/MyPrograms/Go......
  • Linux rocketmq单机测试部署
    一.环境说明对于RocketMQ4.3.0版本,官方要求环境如下,其中Git用于从GitHub获取源码,没有安装也没关系,可以直接下载推荐的流程是:Linux系统上安装Git工具、Maven、JavaJDKGit工具用于直接从GitHub获取RocketMQ项目源码下载到Linux系统上然后Maven将RocketMQ源码......
  • 知识图谱Knowledge Spectrum
    基于图的数据结构,由节点(Point)和边(Edge)组成。在知识图谱里,每个节点表示现实世界中存在的“实体”,每条边为实体与实体之间的“关系”。知识图谱是关系的最有效的表示方式。通俗地讲,知识图谱就是把所有不同种类的信息(HeterogeneousInformation)连接在一起而得到的一个关系网络。......
  • esp32 实时性测试 485 adc
    主循环内测试各个模块的耗时(485/json-parse/ads1115/sht30-dis/)beforesht30:9584aftersht30:9625Temperature:28.52/CHumidity:62.59%beforeadc:9626afteradc:9662hall_sensor_ampere:0.170NTC_Rvalue:10.960looptimeis:9662before485parse9662......
  • leetcode1161最大层内元素之和
    dfslassSolution{public:unordered_map<int,vector<int>>m;voiddfs(TreeNode*root,intdepth){if(!root)return;intres=0;depth++;dfs(root->left,depth);dfs(root->right,depth);......
  • 办公软件套件-office2021 win+mac版
    Office2021是由微软公司开发的一套全面的办公软件套件。作为全球最受欢迎的办公软件之一,Office2021提供了一系列强大的工具和功能,帮助用户在各种工作场景中高效地处理文档、数据、演示和电子邮件等任务。→→↓↓载Office2021mac/win 首先,Office2021包含了经典的文字处理......
  • 解决代码使用CompletableFuture做异步时spring-cloud-starter-sleuth的日志追踪号为空
    产生问题原因就是异步调用,导致spanId和traceId丢失了@Async注解的异步调用是没问题的前提使用spring-cloud-starter-sleuthjar包版本2.2.8.RELEASE关于追踪号的xml配置为<pattern>%yellow(%date{yyyy-MM-ddHH:mm:ss.SSS})[%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B......
  • BUUCTF [极客大挑战 2019]FinalSQL
    通过尝试发现注入点在search.php。传递?id=1^1报ERROR!!!;传递?id=1^0报NO!Notthis!Clickothers~~~布尔盲注importrequestsimporttimeurl="http://eab3a4cf-d57d-4236-a9f9-1383446ba4e1.node4.buuoj.cn:81/search.php?"result=''temp={"id":......
  • Springboot+Quartz+Dynamic-datasource
    在使用dynamic-datasource多数据源切换场景下,实现Quartz任务持久化配置和API动态调度1.pom依赖暂未找到版本对应关系,若有版本不一致异常,请自行尝试升降版本。<dependencies><!--动态数据源--><dependency><groupId>com.baomidou</groupI......
  • CKEditor实现WORD粘贴公式自动上传
    ​ 在之前在工作中遇到在富文本编辑器中粘贴图片不能展示的问题,于是各种网上扒拉,终于找到解决方案,在这里感谢一下知乎中众大神以及TheViper。通过知乎提供的思路找到粘贴的原理,通过TheViper找到粘贴图片的方法。其原理为一下步骤:监听粘贴事件;【用于插入图片】获取光标位置;【......