dotnet6 C# 一个国内还能用的 NTP 时间校准客户端的实现
本文来记录一个我自己在使用的 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);
}
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 文件夹
更多博客,请参阅我的 博客导航
dotnet6 C# 一个国内还能用的 NTP 时间校准客户端的实现的更多相关文章
- 国内常用的几个NTP时间服务器
问题描述: 经常Windows或者Linux系统上面的时间跟我们本地的时间不一致 有时候就是Windows的Internet时间设置里面的Windows自带的时间同步服务器不好使 Linux配置NTP ...
- 为你的pip更换一个国内的镜像源
为你的pip更换一个国内的镜像源 是否常常为pypi官网被无故和谐掉导致pip不能下载python的各个包而痛心疾首? 是否常常在深夜里看着pip install 下载包的速度慢如乌龟而长吁短叹? 是 ...
- mysql实战45讲 (三) 事务隔离:为什么你改了我还看不见 极客时间读书笔记
提到事务,你肯定不陌生,和数据库打交道的时候,我们总是会用到事务.最经典的例子就是转账,你要给朋友小王转100块钱,而此时你的银行卡只有100块钱. 转账过程具体到程序里会有一系列的操作,比如查询余额 ...
- 还在用SimpleDateFormat格式化时间?小心经理锤你
还在用SimpleDateFormat格式化时间?小心经理锤你 场景 本来开开心心的周末时光,线上突然就疯狂报错,以为程序炸了,截停日志,发现是就是类似下述一段错误 java.lang.NumberF ...
- 从一个国内普通开发者的视角谈谈Sitecore
一.Sitecore是个神马玩意 简而言之,Sitecore就是一个基于ASP.NET技术的CMS系统,它不仅具有传统Web CMS的所有功能,还集成了Marketing营销(当然,这个功能价格不菲) ...
- 充满未来和科幻的界面设计FUI在国内还没有起步在国外早起相当成熟
所谓FUI可以是幻想界面(Fantasy User Interfaces).科幻界面(Fictional User Interfaces).假界面(Fake User Interfaces).未来主义 ...
- 发现一个国内牛逼的maven仓库,速度真的太快了
前天网上下了一个项目,在公司还好,网络比较流畅,很快就把依赖下好了:回家的时候,想耍耍,结果下了一天也没把依赖下好,速度是几k每秒,甚至一k每秒,哎~心都碎了,网上一搜,结果发现了一个惊天的用nexu ...
- pip操作以及window和虚拟机中为pip更换一个国内的镜像源的方法
前言 在学习PyQt5的过程中,参考王硕和孙洋洋的PyQt5快速开发与实战中,看到的关于Python开发技巧与实战,觉得挺好的 所以将其摘抄了下来方便阅读.之后还有一个关于更换pip镜像源的方法,方便 ...
- 分享一个国内首个企业级开源的GO语言网关--GoKu API Gateway
一. 简介 GoKu API Gateway,中文名:悟空API网关,是国内首个开源go语言API网关,帮助企业进行API服务治理与API性能安全维护,为企业数字化赋能. GoKu API Gatew ...
- 一个 static 还能难得住我?
static 是我们日常生活中经常用到的关键字,也是 Java 中非常重要的一个关键字,static 可以修饰变量.方法.做静态代码块.静态导包等,下面我们就来具体聊一聊这个关键字,我们先从基础开始, ...
随机推荐
- django(ORM)
一 单表(增.删.改.查) 1 测试脚本 ''' 只想测试django中的某一个py文件内容,那么可以不用书写前后端交互的形式 而是直接写一个测试脚本即可 ''' # 脚本代码无论是写在应用下的tes ...
- 记录--get请求参数放在body中?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 1.背景 与后端对接口时,看到有一个get请求的接口,它的参数是放在body中的 ******get请求参数可以放在body中?? 随即问 ...
- 记录--基于css3写出的流光登录(注释超详细!)
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 完整效果 对基本的表单样式进行设置 这里设置了基本的表单样式,外层用了div进行包裹,重点是运用了两个i元素在后期通过css样式勾画出一条 ...
- C# Demo 资源汇总
1.OCR 相关OCRService Onnx版 https://lw112190.blog.csdn.net/article/details/132082357 OCRService Sdcb.Pa ...
- linux 时钟同步
yum install ntp -y #cn.pool.ntp.org ntp[1-7].aliyun.com ntpdate ntp1.aliyun.com #把当前系统时间写入到CMOS中 clo ...
- 可视化学习:实现Canvas图片局部放大镜
前言 最近我在可视化课程中学习了如何在Canvas中利用像素处理来实现滤镜效果,在这节课程的结尾留了一道局部放大镜的题目,提示我们用像素处理的方式去实现这个效果,最终实现随着鼠标移动将图片局部放大,本 ...
- KingbaseES sys_restore 恢复表时默认不包括表上的索引
前言 最近碰到一个案例,在使用sys_restore恢复指定表时,默认不恢复表上的索引,如果想恢复需要单独指定. 测试过程 [](javascript:void(0) 查看表的有关属性:test=# ...
- CI和自动化测试的结合(jenkins的搭建和使用)
CI持续集成是一种思想,具体实现是通过jenkins持续集成工具去实现的. Jenkins的安装和配置(war包启动) 安装jenkins的方式有两种: 方式一:通过下载安装包安装,jenkins.m ...
- 一文弄懂java中的Queue家族
目录 简介 Queue接口 Queue的分类 BlockingQueue Deque TransferQueue 总结 java中Queue家族简介 简介 java中Collection集合有三大家族 ...
- OpenHarmony社区运营报告(2023年8月)
本月快讯 ● 2023年8月3日,OpenAtom OpenHarmony(以下简称"OpenHarmony")发布了Beta2版本.OpenHarmony 4.0 Beta2 ...