张高兴的 .NET IoT 入门指南:(八)基于 GPS 的 NTP 时间同步服务器 ...

打印 上一主题 下一主题

主题 894|帖子 894|积分 2682

时间究竟是什么?这既可以是一个哲学问题,也可以是一个物理问题。古人对太阳进行观测,利用太阳的投影发明了日晷,定义了最初的时间。随着科技的发展,天文观测的精度也越来越准确,人们发现地球的自转并不是完全一致的,这就导致每天经过的时间是不一样的。这点误差对于基本生活基本没有影响,但是对于股票交易、火箭发射等等要求高精度时间的场景就无法忍受了。科学家们开始把观测转移到了微观世界,找到了一种运动高度稳定的原子——铯,最终定义出了准确的时间:铯原子电子跃迁 9192631770 个周期所持续的时间长度定义为 1 秒。基于这个定义制造出了高度稳定的原子钟。
时间在计算机中又是如何定义的呢?通常使用 Unix 时间戳进行表示,记录的是自公元 1970 年 1 月 1 日 0 时 0 分 0 秒以来的秒数。计算机为了维持时钟的走时,硬件层面使用晶体振荡器保障时钟的精确性(也是石英钟的原理),操作系统层面使用时钟中断去更新时间的流逝。现代计算机的硬件设计通常有独立的时钟(RTC),这源于 Intel 和微软创立的标准 High Precision Event Timer(HPET),标准指定了 10 MHz 的时钟速度,因此时钟可以获得 100 纳秒的分辨率。这也是 .NET 时间有关的类型中 Ticks 属性的由来,1秒 = 10000000 Ticks。虽然计算机的时钟已经足够精准,但也会受到环境温度的影响造成过快或者过慢的问题。为了对计算机的时钟进行校准,通常使用 NTP 协议与网络中的时间服务器进行同步。时间服务器的时间又会使用 GPS 接收机、无线电或者是原子钟进行校准。
本文将从 GPS 时间的获取、NTP 报文的编写实现一个“玩具”级别的时间同步服务器,使用 .NET 6 编写一个控制台应用程序,通过本文你可以学到:

  • 串口 SerialPort 类的使用;
  • 使用 Socket 类实现 UDP 的监听与回复;
  • 在程序中使用 Process 类执行命令行指令;
  • 了解 GPS 数据报文的 NMEA-0183 协议;
  • 了解 NTP 协议报文。

硬件需求

名称描述数量计算机可以是运行 Linux 的开发板,也可以是运行 Windows 的电脑x1NEO-6MGPS 模块x1USB 串口可选,使用 USB 串口将 GPS 模块与计算机相连x1杜邦线传感器与开发板的连接线若干电路


传感器接口开发板接口NEO-6MTX开发板或 USB 串口的RXRX开发板或 USB 串口的TXVCC5VGNDGNDGPS 数据报文的 NMEA-0183 协议

NMEA-0183 是 GPS 设备输出信息的标准格式,是由美国国家海洋电子协会(National Marine Electronics Association)定制的标准。NMEA-0183 有多种不同的数据报文,每种都是独立的 ASCII 字符串,使用逗号隔开数据,数据流长度从 30-100 字符不等,通常以每秒间隔选择输出。NMEA-0183 协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有 $GPGGA、$GPGSA、$GPGSV、$GPRMC、$GPVTG 等。下面给出这些常用 NMEA-0183 语句的解释。
帧名称说明最大帧长$GPGGA全球定位数据72$GPGSA卫星 PRN 数据65$GPGSV卫星状态信息210$GPRMC推荐最小数据70$GPVTG地面速度信息34由于我们只需要从 GPS 中获取时间信息,选择包含时间信息的 “$GPRMC 推荐最小数据”帧进行解析:
$GPRMC*帧头UTC 时间定位状态纬度纬度半球经度经度半球地面速率地面航向UTC 日期磁偏角磁偏角方向模式 * 校验和下面以一个真实的数据帧为例 $GPRMC,013717.00,A,3816.57392,N,10708.73951,E,0.467,,050722,,,A*78:
$GPRMC013717.00A3816.57392N10708.73951E0.467050722A*78帧头UTC 时间 01:37:17A=有效定位,V=无效定位纬度 38 度 16.57392 分北纬经度 107 度 8.73951 分东经地面速率 0.467 节航向 度UTC 日期 2022/07/05磁偏角 度磁偏角方向A=自主定位,N=数据无效因此,通过串口读取 $GPRMC 数据帧后,需要解析  和  字段的值,并将其转换为 UTC 时间。
细心的你也许会发现获取到的时间信息只精确到秒,GPS 明明使用的是原子钟,这是为什么?仔细观察手中的 GPS 模块,还有一个 PPS 针脚没有使用。PPS(Pulse Per Second)是秒脉冲,一般是由 GPS 接收机或原子钟按秒发出的、宽度小于1秒、有着急升或突降边沿的脉冲信号,通常用于精确计时和测量时间。PPS 信号能精确地(亚毫秒级)指示每一秒的开始时间,但不能指示对应现实时间的哪一秒,因此只能作为辅助信号,与卫星导航信息组合使用,提供低延迟、低抖动的授时服务。很遗憾,.NET 目前没法直接操作 PPS 引脚,我们只能实现一个“玩具”级的时间同步服务器了。
NTP 协议报文

NTP(Network Time Protocol),网络时间协议,是一种使用 UDP 的计算机之间进行时间同步的网络协议,位于 OSI 7 层网络模型中的应用层,默认使用的端口为 123。那么使用 NTP 是如何进行时间同步的呢?简单的说将发送的报文打上本机的时间戳,配合报文来回传输的时延修正本机的时间。如下图所示,可以计算出网络传输时延 \(\delta\),以及客户端与服务端的时间偏移 \(\theta\):

\(\delta=(t_3-t_0)-(t_2-t_1)\)
\(\theta=\frac{(t_1-t_0)+(t_2-t_3)}{2}\)
其中,\(t_0\) 是请求报文传输的客户端时间戳,\(t_1\) 是请求报文接收的服务器时间戳,\(t_2\) 是回复报文传输的服务器时间戳,\(t_3\) 是回复报文接收的客户端时间戳。客户端和服务端都有一个时间轴,分别代表着各自系统的时间,当客户端想要同步服务端的时间时,客户端会构造一个 NTP 报文发送到服务端,客户端会记下此时发送的时间 \(t_0\),经过一段网络延时传输后,服务器在 \(t_1\) 时刻收到报文,经过一段时间处理后在 \(t_2\) 时刻向客户端返回报文,再经过一段网络延时传输后客户端在 \(t_3\) 时刻收到服务器报文。这样客户端就可以校准自己的本机时间了。
在了解 NTP 同步时间的过程后,下面解析 NTP 报文具体包含的字段,一般的 NTP 报文长度为 48 字节:
字段说明LI闰秒指示,2bitVersionNTP 版本,3bitMode工作模式,3bit ,客户端=0b011,服务器=0b100Stratum时钟层数,8bit,层数为 0 的设备为高精度的时钟(如原子钟),层数为 1 的设备与层数 0 的设备直接相连,……Poll Interval轮询时间,8bit,连续 NTP 报文之间的最大时间间隔Precision时钟精度,8bitRoot Delay根时延,32bit,表示在主参考源之间往返的总共时延Root Dispersion根离散,32bit,相对于主参考源的标称误差Reference ID参考源的标识,32bit,4 个字符或 IP 地址Reference Timestamp参考时间戳,64bit,本地时钟最后一次被更新的时间| Originate Timestamp | 原始时间戳 \(t_0\),64bit,客户端发送的时间 |
| Receive Timestamp | 接受时间戳 \(t_1\),64bit,服务端接受到的时间 |
| Transmit Timestamp | 传送时间戳 \(t_2\),64bit,服务端发送的时间 |
其中要注意的是 NTP 时间戳的起始时间是 1900-01-01 00:00:00,而不是 Unix 时间戳的起始时间 1970-01-01 00:00:00。
下面是使用 Wireshark 抓取的 Windows 时钟同步的 NTP 报文:

编写代码

项目地址:https://github.com/ZhangGaoxing/gps-ntp
项目结构

创建一个控制台应用和类库,项目结构如下:

项目依赖

添加如下 NuGet 包引用:
  1. <ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup>
复制代码
配置串口读取 GPS 数据

绝大部分 GPS 模块每秒会通过串口输出 NMEA-0183 协议报文,因此我们只需要通过串口读取需要的时间数据即可。此环节包含 3 个步骤:

  • 初始化串口;
  • 读取 $GPRMC 数据帧的内容,提取时间信息;
  • 更新系统时间。
初始化串口

使用串口时最重要的属性是波特率,请查阅对应 GPS 模块的数据手册,这里使用的 NEO-6M 模块的波特率是 9600。串口的名称取决于你的连接方式,在 Linux 中串口对应的驱动文件在 /dev 目录下,使用内置串口可能的文件名称为 ttySx,使用 USB 串口可能的文件名称为 ttyUSBx,在 Windows 中串口的名称为 COMx,其中 x 表示的是数字编号。
  1. // 使用的串口名称const string SERIAL_NAME = "/dev/ttyUSB0";using SerialPort gps = new SerialPort(SERIAL_NAME){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> BaudRate = 9600,<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> Encoding = Encoding.UTF8,<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup> ReadTimeout = 500,<ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup> WriteTimeout = 500,};
复制代码
从串口中获取数据

从串口中读取数据时使用的是 SerialPort 类中的 DataReceived 事件。事件(event)可以理解为一种广播,当完成某种操作后向外发送通知。即串口接收到数据后,触发数据处理事件。
  1. gps.DataReceived += GpsFrameReceived;/// /// GPS 报文处理/// void GpsFrameReceived(object sender, SerialDataReceivedEventArgs e){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> // TODO:读取 `$GPRMC` 数据帧;提取时间;更新系统时间}
复制代码
由于 GPS 模块输出的不只有 $GPRMC 数据帧,因此需要在处理事件中判断帧头以及帧的有效性。
  1. void GpsFrameReceived(object sender, SerialDataReceivedEventArgs e){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> string frame = gps.ReadLine();<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> if (frame.StartsWith("$GPRMC"))<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup> {<ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup><ItemGroup>
  10.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  11. </ItemGroup>  // $GPRMC,UTC 时间,定位状态,纬度,纬度半球,经度,经度半球,速度,航向,UTC 日期,磁偏角,磁偏角方向,指示模式*校验和<ItemGroup>
  12.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  13. </ItemGroup><ItemGroup>
  14.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  15. </ItemGroup>  // $GPRMC,013717.00,A,3816.57392,N,10708.73951,E,0.467,,050722,,,A*78<ItemGroup>
  16.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  17. </ItemGroup><ItemGroup>
  18.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  19. </ItemGroup>  string[] field = frame.Split(',');<ItemGroup>
  20.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  21. </ItemGroup><ItemGroup>
  22.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  23. </ItemGroup>  // 帧数据有效<ItemGroup>
  24.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  25. </ItemGroup><ItemGroup>
  26.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  27. </ItemGroup>  if (!field[12].StartsWith("N"))<ItemGroup>
  28.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  29. </ItemGroup><ItemGroup>
  30.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  31. </ItemGroup>  {<ItemGroup>
  32.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  33. </ItemGroup><ItemGroup>
  34.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  35. </ItemGroup><ItemGroup>
  36.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  37. </ItemGroup><ItemGroup>
  38.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  39. </ItemGroup>// TODO:提取时间;更新系统时间<ItemGroup>
  40.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  41. </ItemGroup><ItemGroup>
  42.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  43. </ItemGroup>  }<ItemGroup>
  44.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  45. </ItemGroup> }}
复制代码
在验证 $GPRMC 数据帧有效后,根据帧解析提取对应字段的时间信息。
  1. void GpsFrameReceived(object sender, SerialDataReceivedEventArgs e){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> string frame = gps.ReadLine();<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> if (frame.StartsWith("$GPRMC"))<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup> {<ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup><ItemGroup>
  10.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  11. </ItemGroup>  string[] field = frame.Split(',');<ItemGroup>
  12.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  13. </ItemGroup><ItemGroup>
  14.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  15. </ItemGroup>  if (!field[12].StartsWith("N"))<ItemGroup>
  16.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  17. </ItemGroup><ItemGroup>
  18.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  19. </ItemGroup>  {<ItemGroup>
  20.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  21. </ItemGroup><ItemGroup>
  22.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  23. </ItemGroup><ItemGroup>
  24.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  25. </ItemGroup><ItemGroup>
  26.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  27. </ItemGroup>// 获取 GPS 时间<ItemGroup>
  28.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  29. </ItemGroup><ItemGroup>
  30.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  31. </ItemGroup><ItemGroup>
  32.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  33. </ItemGroup><ItemGroup>
  34.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  35. </ItemGroup>string time = field[1][0..6];<ItemGroup>
  36.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  37. </ItemGroup><ItemGroup>
  38.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  39. </ItemGroup><ItemGroup>
  40.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  41. </ItemGroup><ItemGroup>
  42.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  43. </ItemGroup>string date = field[9];<ItemGroup>
  44.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  45. </ItemGroup><ItemGroup>
  46.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  47. </ItemGroup><ItemGroup>
  48.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  49. </ItemGroup><ItemGroup>
  50.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  51. </ItemGroup>DateTime utcNow = DateTime.ParseExact($"{date}{time}", "ddMMyyHHmmss", CultureInfo.InvariantCulture);<ItemGroup>
  52.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  53. </ItemGroup><ItemGroup>
  54.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  55. </ItemGroup><ItemGroup>
  56.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  57. </ItemGroup><ItemGroup>
  58.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  59. </ItemGroup>// TODO:更新系统时间<ItemGroup>
  60.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  61. </ItemGroup><ItemGroup>
  62.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  63. </ItemGroup>  }<ItemGroup>
  64.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  65. </ItemGroup> }}
复制代码
更新系统时间

由于 .NET 并不提供修改系统时间的操作,因此我们要使用间接的方式修改系统时间。一种方式是使用 P/Invoke 调用 C++ 的函数,这种方式可以精确的修改时间,但涉及引用、数据类型转换,过于复杂,和本入门指南不符。这里使用的是运行命令行指令的方式修改系统的时间,但修改时间的精度只能精确到秒。在 Windows 中使用 PowerShell 的 Set-Date 命令,在 Linux 中使用 date 命令。
  1. /// /// 更新系统时间/// void UpdateSystemTime(DateTime time){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> ProcessStartInfo processInfo;<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup> {<ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup><ItemGroup>
  10.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  11. </ItemGroup>  processInfo = new ProcessStartInfo<ItemGroup>
  12.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  13. </ItemGroup><ItemGroup>
  14.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  15. </ItemGroup>  {<ItemGroup>
  16.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  17. </ItemGroup><ItemGroup>
  18.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  19. </ItemGroup><ItemGroup>
  20.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  21. </ItemGroup><ItemGroup>
  22.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  23. </ItemGroup>FileName = "powershell.exe",<ItemGroup>
  24.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  25. </ItemGroup><ItemGroup>
  26.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  27. </ItemGroup><ItemGroup>
  28.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  29. </ItemGroup><ItemGroup>
  30.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  31. </ItemGroup>Arguments = $"Set-Date """{time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")}"""",<ItemGroup>
  32.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  33. </ItemGroup><ItemGroup>
  34.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  35. </ItemGroup><ItemGroup>
  36.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  37. </ItemGroup><ItemGroup>
  38.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  39. </ItemGroup>RedirectStandardOutput = true,<ItemGroup>
  40.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  41. </ItemGroup><ItemGroup>
  42.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  43. </ItemGroup><ItemGroup>
  44.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  45. </ItemGroup><ItemGroup>
  46.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  47. </ItemGroup>UseShellExecute = false,<ItemGroup>
  48.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  49. </ItemGroup><ItemGroup>
  50.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  51. </ItemGroup><ItemGroup>
  52.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  53. </ItemGroup><ItemGroup>
  54.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  55. </ItemGroup>CreateNoWindow = true,<ItemGroup>
  56.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  57. </ItemGroup><ItemGroup>
  58.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  59. </ItemGroup>  };<ItemGroup>
  60.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  61. </ItemGroup> }<ItemGroup>
  62.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  63. </ItemGroup> else<ItemGroup>
  64.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  65. </ItemGroup> {<ItemGroup>
  66.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  67. </ItemGroup><ItemGroup>
  68.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  69. </ItemGroup>  processInfo = new ProcessStartInfo<ItemGroup>
  70.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  71. </ItemGroup><ItemGroup>
  72.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  73. </ItemGroup>  {<ItemGroup>
  74.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  75. </ItemGroup><ItemGroup>
  76.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  77. </ItemGroup><ItemGroup>
  78.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  79. </ItemGroup><ItemGroup>
  80.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  81. </ItemGroup>FileName = "date",<ItemGroup>
  82.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  83. </ItemGroup><ItemGroup>
  84.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  85. </ItemGroup><ItemGroup>
  86.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  87. </ItemGroup><ItemGroup>
  88.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  89. </ItemGroup>Arguments = $"-s "{time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")}"",<ItemGroup>
  90.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  91. </ItemGroup><ItemGroup>
  92.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  93. </ItemGroup><ItemGroup>
  94.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  95. </ItemGroup><ItemGroup>
  96.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  97. </ItemGroup>RedirectStandardOutput = true,<ItemGroup>
  98.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  99. </ItemGroup><ItemGroup>
  100.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  101. </ItemGroup><ItemGroup>
  102.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  103. </ItemGroup><ItemGroup>
  104.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  105. </ItemGroup>UseShellExecute = false,<ItemGroup>
  106.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  107. </ItemGroup><ItemGroup>
  108.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  109. </ItemGroup><ItemGroup>
  110.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  111. </ItemGroup><ItemGroup>
  112.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  113. </ItemGroup>CreateNoWindow = true,<ItemGroup>
  114.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  115. </ItemGroup><ItemGroup>
  116.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  117. </ItemGroup>  };<ItemGroup>
  118.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  119. </ItemGroup> }<ItemGroup>
  120.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  121. </ItemGroup> var process = Process.Start(processInfo);<ItemGroup>
  122.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  123. </ItemGroup> process.WaitForExit();}
复制代码
最终报文处理事件由以下代码构成:
  1. void GpsFrameReceived(object sender, SerialDataReceivedEventArgs e){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> string frame = gps.ReadLine();<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> if (frame.StartsWith("$GPRMC"))<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup> {<ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup><ItemGroup>
  10.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  11. </ItemGroup>  string[] field = frame.Split(',');<ItemGroup>
  12.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  13. </ItemGroup><ItemGroup>
  14.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  15. </ItemGroup>  if (!field[12].StartsWith("N"))<ItemGroup>
  16.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  17. </ItemGroup><ItemGroup>
  18.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  19. </ItemGroup>  {<ItemGroup>
  20.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  21. </ItemGroup><ItemGroup>
  22.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  23. </ItemGroup><ItemGroup>
  24.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  25. </ItemGroup><ItemGroup>
  26.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  27. </ItemGroup>string time = field[1][0..6];<ItemGroup>
  28.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  29. </ItemGroup><ItemGroup>
  30.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  31. </ItemGroup><ItemGroup>
  32.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  33. </ItemGroup><ItemGroup>
  34.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  35. </ItemGroup>string date = field[9];<ItemGroup>
  36.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  37. </ItemGroup><ItemGroup>
  38.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  39. </ItemGroup><ItemGroup>
  40.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  41. </ItemGroup><ItemGroup>
  42.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  43. </ItemGroup>DateTime utcNow = DateTime.ParseExact($"{date}{time}", "ddMMyyHHmmss", CultureInfo.InvariantCulture);<ItemGroup>
  44.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  45. </ItemGroup><ItemGroup>
  46.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  47. </ItemGroup><ItemGroup>
  48.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  49. </ItemGroup><ItemGroup>
  50.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  51. </ItemGroup>UpdateSystemTime(utcNow);<ItemGroup>
  52.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  53. </ItemGroup><ItemGroup>
  54.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  55. </ItemGroup><ItemGroup>
  56.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  57. </ItemGroup><ItemGroup>
  58.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  59. </ItemGroup>// 记录时钟最后一次被更新的时间<ItemGroup>
  60.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  61. </ItemGroup><ItemGroup>
  62.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  63. </ItemGroup><ItemGroup>
  64.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  65. </ItemGroup><ItemGroup>
  66.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  67. </ItemGroup>lastUpdatedTime = utcNow;<ItemGroup>
  68.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  69. </ItemGroup><ItemGroup>
  70.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  71. </ItemGroup>  }<ItemGroup>
  72.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  73. </ItemGroup> }}
复制代码
使用 gps.Open(); 打开串口后就可以获取时间数据了。
实现 NTP 服务

下面使用 Socket 类实现一个简单的 UDP 服务器,用于监听和回复 NTP 报文。
初始化 UDP 服务
  1. // NTP 服务初始化
  2. using Socket ntpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  3. IPEndPoint ip = new IPEndPoint(IPAddress.Any, 123);
  4. ntpServer.Bind(ip);
复制代码
监听和回复 NTP 报文

在后台新建一个进程用于监听 NTP 请求报文:
  1. new Thread(NtpFrameReceived){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> IsBackground = true}.Start();/// /// NTP 报文接收与回复/// void NtpFrameReceived(){<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> // 存储接收到的 NTP 请求报文<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup> Span receiveFrame = stackalloc byte[48];<ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup> while (true)<ItemGroup>
  10.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  11. </ItemGroup> {<ItemGroup>
  12.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  13. </ItemGroup><ItemGroup>
  14.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  15. </ItemGroup>  // 接收请求报文<ItemGroup>
  16.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  17. </ItemGroup><ItemGroup>
  18.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  19. </ItemGroup>  EndPoint clientPoint = new IPEndPoint(IPAddress.Any, 0);<ItemGroup>
  20.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  21. </ItemGroup><ItemGroup>
  22.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  23. </ItemGroup>  ntpServer.ReceiveFrom(receiveFrame, ref clientPoint);<ItemGroup>
  24.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  25. </ItemGroup><ItemGroup>
  26.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  27. </ItemGroup>  DateTime receiveTime = DateTime.UtcNow;<ItemGroup>
  28.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  29. </ItemGroup><ItemGroup>
  30.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  31. </ItemGroup>  // TODO:回复 NTP 报文<ItemGroup>
  32.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  33. </ItemGroup> }}
复制代码
根据帧解析生成 NTP 回复报文:
  1. /// /// 生成 NTP 报文/// Span GenerateNtpFrame(Span receivedFrame, DateTime receiveTime){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> Span ntpFrame = stackalloc byte[48]<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> {<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup><ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup>  0x1c, 0x01, 0x11, 0xe9, 0x00, 0x00, 0x00, 0x00,<ItemGroup>
  10.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  11. </ItemGroup><ItemGroup>
  12.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  13. </ItemGroup>  0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,<ItemGroup>
  14.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  15. </ItemGroup><ItemGroup>
  16.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  17. </ItemGroup>  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,<ItemGroup>
  18.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  19. </ItemGroup><ItemGroup>
  20.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  21. </ItemGroup>  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,<ItemGroup>
  22.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  23. </ItemGroup><ItemGroup>
  24.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  25. </ItemGroup>  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,<ItemGroup>
  26.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  27. </ItemGroup><ItemGroup>
  28.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  29. </ItemGroup>  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,<ItemGroup>
  30.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  31. </ItemGroup> };<ItemGroup>
  32.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  33. </ItemGroup> // Client Transmit Timestamp => Server Origin Timestamp<ItemGroup>
  34.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  35. </ItemGroup> for (int i = 0; i < 8; i++)<ItemGroup>
  36.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  37. </ItemGroup> {<ItemGroup>
  38.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  39. </ItemGroup><ItemGroup>
  40.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  41. </ItemGroup>  ntpFrame[24 + i] = receivedFrame[40 + i];<ItemGroup>
  42.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  43. </ItemGroup> }<ItemGroup>
  44.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  45. </ItemGroup> // 本机时钟最后更新时间<ItemGroup>
  46.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  47. </ItemGroup> long referenceTicks = (lastUpdatedTime - ntpStart).Ticks;<ItemGroup>
  48.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  49. </ItemGroup> uint referenceTimeInt = (uint)(referenceTicks / TICK_2_SECOND);<ItemGroup>
  50.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  51. </ItemGroup> uint referenceTimeFract = (uint)(referenceTicks % TICK_2_SECOND);<ItemGroup>
  52.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  53. </ItemGroup> var referenceTimeIntByte = BitConverter.GetBytes(referenceTimeInt);<ItemGroup>
  54.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  55. </ItemGroup> var referenceTimeFractByte = BitConverter.GetBytes(referenceTimeFract);<ItemGroup>
  56.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  57. </ItemGroup> // 接收报文时间<ItemGroup>
  58.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  59. </ItemGroup> long receiveTicks = (receiveTime - ntpStart).Ticks;<ItemGroup>
  60.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  61. </ItemGroup> uint receiveTimeInt = (uint)(receiveTicks / TICK_2_SECOND);<ItemGroup>
  62.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  63. </ItemGroup> uint receiveTimeFract = (uint)(receiveTicks % TICK_2_SECOND);<ItemGroup>
  64.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  65. </ItemGroup> var receiveTimeIntByte = BitConverter.GetBytes(receiveTimeInt);<ItemGroup>
  66.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  67. </ItemGroup> var receiveTimeFractByte = BitConverter.GetBytes(receiveTimeFract);<ItemGroup>
  68.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  69. </ItemGroup> // 发送报文时间<ItemGroup>
  70.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  71. </ItemGroup> long transmitTicks = (DateTime.UtcNow - ntpStart).Ticks;<ItemGroup>
  72.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  73. </ItemGroup> uint transmitTimeInt = (uint)(receiveTicks / TICK_2_SECOND);<ItemGroup>
  74.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  75. </ItemGroup> uint transmitTimeFract = (uint)(receiveTicks % TICK_2_SECOND);<ItemGroup>
  76.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  77. </ItemGroup> var transmitTimeIntByte = BitConverter.GetBytes(receiveTimeInt);<ItemGroup>
  78.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  79. </ItemGroup> var transmitTimeFractByte = BitConverter.GetBytes(receiveTimeFract);<ItemGroup>
  80.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  81. </ItemGroup> if (BitConverter.IsLittleEndian)<ItemGroup>
  82.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  83. </ItemGroup> {<ItemGroup>
  84.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  85. </ItemGroup><ItemGroup>
  86.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  87. </ItemGroup>  for (int i = 0; i < 4; i++)<ItemGroup>
  88.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  89. </ItemGroup><ItemGroup>
  90.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  91. </ItemGroup>  {<ItemGroup>
  92.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  93. </ItemGroup><ItemGroup>
  94.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  95. </ItemGroup><ItemGroup>
  96.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  97. </ItemGroup><ItemGroup>
  98.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  99. </ItemGroup>ntpFrame[19 - i] = referenceTimeIntByte[i];<ItemGroup>
  100.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  101. </ItemGroup><ItemGroup>
  102.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  103. </ItemGroup><ItemGroup>
  104.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  105. </ItemGroup><ItemGroup>
  106.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  107. </ItemGroup>ntpFrame[23 - i] = referenceTimeFractByte[i];<ItemGroup>
  108.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  109. </ItemGroup><ItemGroup>
  110.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  111. </ItemGroup><ItemGroup>
  112.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  113. </ItemGroup><ItemGroup>
  114.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  115. </ItemGroup>ntpFrame[35 - i] = receiveTimeIntByte[i];<ItemGroup>
  116.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  117. </ItemGroup><ItemGroup>
  118.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  119. </ItemGroup><ItemGroup>
  120.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  121. </ItemGroup><ItemGroup>
  122.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  123. </ItemGroup>ntpFrame[39 - i] = receiveTimeFractByte[i];<ItemGroup>
  124.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  125. </ItemGroup><ItemGroup>
  126.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  127. </ItemGroup><ItemGroup>
  128.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  129. </ItemGroup><ItemGroup>
  130.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  131. </ItemGroup>ntpFrame[43 - i] = transmitTimeIntByte[i];<ItemGroup>
  132.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  133. </ItemGroup><ItemGroup>
  134.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  135. </ItemGroup><ItemGroup>
  136.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  137. </ItemGroup><ItemGroup>
  138.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  139. </ItemGroup>ntpFrame[47 - i] = transmitTimeFractByte[i];<ItemGroup>
  140.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  141. </ItemGroup><ItemGroup>
  142.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  143. </ItemGroup>  }<ItemGroup>
  144.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  145. </ItemGroup> }<ItemGroup>
  146.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  147. </ItemGroup> else<ItemGroup>
  148.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  149. </ItemGroup> {<ItemGroup>
  150.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  151. </ItemGroup><ItemGroup>
  152.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  153. </ItemGroup>  for (int i = 0; i < 4; i++)<ItemGroup>
  154.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  155. </ItemGroup><ItemGroup>
  156.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  157. </ItemGroup>  {<ItemGroup>
  158.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  159. </ItemGroup><ItemGroup>
  160.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  161. </ItemGroup><ItemGroup>
  162.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  163. </ItemGroup><ItemGroup>
  164.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  165. </ItemGroup>ntpFrame[16 + i] = referenceTimeIntByte[i];<ItemGroup>
  166.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  167. </ItemGroup><ItemGroup>
  168.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  169. </ItemGroup><ItemGroup>
  170.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  171. </ItemGroup><ItemGroup>
  172.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  173. </ItemGroup>ntpFrame[20 + i] = referenceTimeFractByte[i];<ItemGroup>
  174.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  175. </ItemGroup><ItemGroup>
  176.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  177. </ItemGroup><ItemGroup>
  178.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  179. </ItemGroup><ItemGroup>
  180.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  181. </ItemGroup>ntpFrame[32 + i] = receiveTimeIntByte[i];<ItemGroup>
  182.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  183. </ItemGroup><ItemGroup>
  184.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  185. </ItemGroup><ItemGroup>
  186.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  187. </ItemGroup><ItemGroup>
  188.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  189. </ItemGroup>ntpFrame[36 + i] = receiveTimeFractByte[i];<ItemGroup>
  190.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  191. </ItemGroup><ItemGroup>
  192.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  193. </ItemGroup><ItemGroup>
  194.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  195. </ItemGroup><ItemGroup>
  196.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  197. </ItemGroup>ntpFrame[40 + i] = transmitTimeIntByte[i];<ItemGroup>
  198.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  199. </ItemGroup><ItemGroup>
  200.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  201. </ItemGroup><ItemGroup>
  202.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  203. </ItemGroup><ItemGroup>
  204.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  205. </ItemGroup>ntpFrame[44 + i] = transmitTimeFractByte[i];<ItemGroup>
  206.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  207. </ItemGroup><ItemGroup>
  208.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  209. </ItemGroup>  }<ItemGroup>
  210.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  211. </ItemGroup> }<ItemGroup>
  212.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  213. </ItemGroup> return ntpFrame.ToArray();}
复制代码
最终报文请求与回复由以下代码构成:
  1. void NtpFrameReceived(){<ItemGroup>
  2.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  3. </ItemGroup> Span receiveFrame = stackalloc byte[48];<ItemGroup>
  4.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  5. </ItemGroup> while (true)<ItemGroup>
  6.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  7. </ItemGroup> {<ItemGroup>
  8.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  9. </ItemGroup><ItemGroup>
  10.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  11. </ItemGroup>  EndPoint clientPoint = new IPEndPoint(IPAddress.Any, 0);<ItemGroup>
  12.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  13. </ItemGroup><ItemGroup>
  14.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  15. </ItemGroup>  ntpServer.ReceiveFrom(receiveFrame, ref clientPoint);<ItemGroup>
  16.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  17. </ItemGroup><ItemGroup>
  18.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  19. </ItemGroup>  DateTime receiveTime = DateTime.UtcNow;<ItemGroup>
  20.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  21. </ItemGroup><ItemGroup>
  22.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  23. </ItemGroup>  // 回复 NTP 报文<ItemGroup>
  24.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  25. </ItemGroup><ItemGroup>
  26.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  27. </ItemGroup>  Span sendFrame = GenerateNtpFrame(receiveFrame, DateTime.UtcNow);<ItemGroup>
  28.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  29. </ItemGroup><ItemGroup>
  30.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  31. </ItemGroup>  ntpServer.SendTo(sendFrame, clientPoint);<ItemGroup>
  32.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  33. </ItemGroup><ItemGroup>
  34.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  35. </ItemGroup>  DateTime sendTime = DateTime.UtcNow;<ItemGroup>
  36.    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
  37. </ItemGroup> }}
复制代码
将上述代码进行整合就构成了基于 GPS 的 NTP 时间同步服务器。
部署应用

发布到文件


  • 切换到 GpsNtp 项目运行发布命令:
  1. dotnet publish -c release -r linux-x64 --no-self-contained
复制代码

  • 将发布后的文件通过 FTP 等方式复制到 Linux 开发板;
  • 为 GpsNtp 文件增加可执行权限
  1. sudo chmod +x GpsNtp
复制代码

  • 运行程序
  1. sudo ./GpsNtp
复制代码
构建 Docker 镜像


  • 在项目的根目录中创建 Dockerfile,并将整个项目复制到 Linux 开发板中:
  1. FROM mcr.microsoft.com/dotnet/core/sdk:6.0-focal AS build
  2. WORKDIR /app
  3. # publish app
  4. COPY src .
  5. WORKDIR /app/GpsNtp
  6. RUN dotnet restore
  7. RUN dotnet publish -c release -r linux-arm -o out
  8. # run app
  9. FROM mcr.microsoft.com/dotnet/core/runtime:6.0-focal AS runtime
  10. WORKDIR /app
  11. COPY --from=build /app/GpsNtp/out ./
  12. ENTRYPOINT ["dotnet", "GpsNtp.dll"]
复制代码

  • 切换到项目目录,构建镜像:
  1. docker build -t gps-ntp -f Dockerfile .
复制代码

  • 运行镜像:
  1. docker run --rm -it --device /dev/ttySx gps-ntp
复制代码
程序运行后,使用 Windows 时间同步服务进行一下测试。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

老婆出轨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表