实现基于NTP协议的网络校时功能
无论PC端还是移动端系统都自带时间同步功能,基于的都是NTP协议,这里使用C#来实现基于NTP协议的网络校时功能(也就是实现时间同步)。
1、NTP原理
NTP【Network Time Protocol】是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒),且可介由加密确认的方式来防止恶毒的协议攻击。
先介绍下NTP数据包格式(其标准化文档为RFC2030,NTP版本是第4版本):
1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Reference Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Originate Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Receive Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Transmit Timestamp (64) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Key Identifier (optional) (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| |
| Message Digest (optional) (128) |
| |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
其中协议字段的含义如下所示:
字段名称 单播
请求报文 响应报文
------------------------------------------------
LI 0 0-2
VN 4 3-4
Mode 3 4
Stratum 0 1-14
Poll 0 ignore
Precision 0 ignore
Root Delay 0 ignore
Root Dispersion 0 ignore
Reference Identifier 0 ignore
Reference Timestamp 0 ignore
Originate Timestamp 0 请求报文发送时间(T1)
Receive Timestamp 0 请求报文到达服务端时间(T2)
Transmit Timestamp 本地时间(T1) 服务端应答报文离开时服务端本地时间(T3)
可以看到客户端发送本地时间(T1)过去后,服务端响应报文会将客户端报文发送时间放在字段Originate Timestamp字段中发回来,同时报文中带有请求报文到达服务端的时间(T2)和服务端应答报文离开服务端时的服务端时间(T3),而客户端接收到来自服务端发送的响应报文时的本地时间为T4,根据这四个参数可以计算:
NTP报文的往返时延delay=(T4-T1)-(T3-T2)
客户端与服务端时间差(时钟补偿)offset=((T2-T1)+(T3-T4))/2
以上时间差计算是假定报文往返相同的情况下,如果请求报文时延和响应报文所花费时间不一致,则计算的时间差offset并不准确(一般来说肯定有误差,误差最大为往返时延的1/2),但这点精度还在容忍范围。如此可以计算服务器端时间ServerTime = LocalTime + offset。
2、代码实现
2.1 报文构造
前面已经讲过,发送的报文Mode为3,版本为4,发送时间是本地时间,其余字段为0,代码如下(可选项不用)
private const byte NTPDataLength = ;
// NTP 数据包 (基于RFC 2030)
byte[] NTPData = new byte[NTPDataLength]; //NTP数据包初始化
private void Initialize()
{
//版本4,模式客户端(3)
NTPData[] = 0x1B;
//其他初始化为0
for (int i = ; i < ; i++)
{
NTPData[i] = ;
}
//发送端本地时间
TransmitTimestamp = DateTime.Now;
}
2.2报文发送
NTP协议基于UDP,端口号为123,报文构造好后则发送报文,需要先获取NTP服务器端地址,百度搜索下第一个就是豆瓣的,笔者使用的是上海交通大学网络中心NTP服务器地址ntp.sjtu.edu.cn,参照国外一位作者的代码(该代码写于2001年,后续笔者会对该代码进行部分改动并封装,后面会放出改动的代码),通过域名解析的方式获得IP地址,然后进行连接。
//在DNS服务器中查询NTP服务器的IP 地址(这里就不要输入IP地址了,否则报错)
IPHostEntry hostadd = Dns.GetHostEntry(TimeServer);
IPEndPoint EPhost = new IPEndPoint(hostadd.AddressList[], ); //连接NTP服务器
UdpClient TimeSocket = new UdpClient();
TimeSocket.Connect(EPhost); //初始化NTP数据报文
Initialize();
//发送NTP报文
TimeSocket.Send(NTPData, NTPData.Length);
2.3报文接收
报文接收后,首先要记录接收报文时的本地时间,代码非常简单,如下
NTPData = TimeSocket.Receive(ref EPhost);
//记录接收到报文时的本地时间
ReceptionTimestamp = DateTime.Now;
2.4报文解析
首先介绍下时间格式,如下所示,时间分为秒和秒的小数部分,左边是高位,右边是低位,代码如下:
1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Seconds |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Seconds Fraction (0-padded) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
private ulong GetMilliSeconds(byte offset)
{
ulong intpart = , fractpart = ; for (int i = ; i <= ; i++)
{
intpart = * intpart + NTPData[offset + i];
}
for (int i = ; i <= ; i++)
{
fractpart = * fractpart + NTPData[offset + i];
} ulong milliseconds = intpart * + (fractpart * ) / 0x100000000L;
return milliseconds;
}
主要讲解下秒的小数部分的表示,小数部分由32位整数表示,如果全部为1,并除以以0x100000000,也就是0xFFFFFFFF/0x100000000=0.999999999767(后面的就省略了),可以看到通过换算小数部分最大值可以精确表示到0.999999999,也就是纳秒级别,这里忽略了大约200多皮秒的时间。对我们来说,只要毫秒时间可以了,所以毫秒计算公式为
milliseconds = 1000* fraction / 0x100000000
获得总毫秒时间后换算为具体年月日时间,代码如下
private DateTime ComputeDate(ulong milliseconds)
{
TimeSpan span =TimeSpan.FromMilliseconds((double)milliseconds);
DateTime time = new DateTime(, , );
time += span;
return time;
}
基于此,计算上面所讲的T1、T2、T3
// T1 请求报文客户端时间
public DateTime OriginateTimestamp
{
get
{
return
ComputeDate(GetMilliSeconds(offOriginateTimestamp));
}
} // T2 接收到请求报文时服务器端时间
public DateTime ReceiveTimestamp
{
get
{
DateTime time = ComputeDate(GetMilliSeconds(offReceiveTimestamp));
// 协调世界时转为当地时间
TimeSpan offspan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
return time + offspan;
}
} // T3 响应报文发送时服务器端时间
public DateTime TransmitTimestamp
{
get
{
DateTime time = ComputeDate(GetMilliSeconds(offTransmitTimestamp));
// 协调世界时转为当地时间
TimeSpan offspan = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
return time + offspan;
}
}
这样可以计算得到时钟补偿offset = (ReceiveTimestamp - OriginateTimestamp) - (ReceptionTimestamp - TransmitTimestamp)
3、代码封装
代码封装基于原国外代码基础之上,重复造车轮意义不大,原代码没有进行时钟补偿,直接使用了服务器端发送时间即 TransmitTimestamp(T3),对其封装后直接获取当前时间就可以了,不用再做修改了,代码如下(比较简单,就没有注释了)
public class BeijingTime
{
private const string HOST = "ntp.sjtu.edu.cn"; private static BeijingTime _instance = null;
private NTPClient _client; private TimeSpan _tsClock = new TimeSpan(); private bool _IsConnect = false; //没有建立连接 private BeijingTime()
{
_client = new NTPClient(HOST);
} public bool IsConnect
{
get { return _IsConnect; }
} public DateTime BeijingTimeNow
{
get { return DateTime.Now.Add(_tsClock); }
} /// <summary>
/// 设置本地时间,返回失败可能是因为权限不足,请在管理员权限下使用
/// </summary>
/// <param name="dtLocal"></param>
/// <returns></returns>
public bool SetLocalTime(DateTime dtLocal)
{
return _client.SetTime(dtLocal);
} public bool Connect()
{
try
{
_client.Connect();
_IsConnect = true;
_tsClock = new TimeSpan(_client.LocalClockOffset); return true;
}
catch (Exception)
{
_IsConnect = false;
return false;
}
} public static BeijingTime Instance
{
get
{
if (_instance == null)
{
_instance = new BeijingTime();
} return _instance;
}
}
}
4、测试结果
下载封装好的代码,如下方式调用
static void Main(string[] args)
{
Utility.BeijingTime beijing = Utility.BeijingTime.Instance;
beijing.Connect();
Console.WriteLine(string.Format("时钟补偿:{0:f6}",(beijing.BeijingTimeNow - DateTime.Now).TotalSeconds));
Console.WriteLine(string.Format("本地时间:{0}",beijing.BeijingTimeNow.ToString()));
Console.ReadLine();
}
结果如下:因为本身使用Windows自带同步功能同步过,所以结果还是蛮精确的
5、后记
网上虽然有很多相关介绍的文章,但个别地方讲的并不仔细,大多代码也不能直接拿来用,就参照国外的源代码和RFC2030文档写了这篇文章,并修改了代码,方便不愿意看原理的人直接下载代码就可以使用。NTP协议内容很多,这里只讲了客户端请求服务端的方式。限于笔者个人水平,文章中难免会出现疏漏,还望指正。
参考文章
1、http://blog.sina.com.cn/s/blog_772ee6f30100pbzw.html
2、http://www.ietf.org/rfc/rfc2030.txt
3、http://blog.163.com/yzc_5001/blog/static/2061963420121283050787/
实现基于NTP协议的网络校时功能的更多相关文章
- 北斗时钟同步系统-GPS卫星授时设备-NTP网络校时服务器
北斗时钟同步系统-GPS卫星授时设备-NTP网络校时服务器 北斗时钟同步系统-GPS卫星授时设备-NTP网络校时服务器 北斗时钟同步系统-GPS卫星授时设备-NTP网络校时服务器 论述当下网络时间同步 ...
- 网络时钟服务器,NTP授时设备,北斗网络校时服务器,GPS时间同步器
网络时钟服务器,NTP授时设备,北斗网络校时服务器,GPS时间同步器 网络时钟服务器,NTP授时设备,北斗网络校时服务器,GPS时间同步器 论述当下网络时间同步的重要性 北京华人开创科技发展有限公 ...
- 用c++开发基于tcp协议的文件上传功能
用c++开发基于tcp协议的文件上传功能 2005我正在一家游戏公司做程序员,当时一直在看<Windows网络编程> 这本书,把里面提到的每种IO模型都试了一次,强烈推荐学习网络编程的同学 ...
- Android消息推送(二)--基于MQTT协议实现的推送功能
国内的Android设备,不能稳定的使用Google GCM(Google Cloud Messageing)消息推送服务. 1. 国内的Android设备,基本上从操作系统底层开始就去掉了Googl ...
- 基于UDP协议的网络编程
UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送.接收数据报的对象. Java使用DatagramSock ...
- 通过python基于netconf协议获取网络中网元的配置数据,助力企业网络控制自动化轻松实现!
摘要:在当今信息化时代,大多数企业都需要网络支撑企业的ICT运行,提升企业运行效率,针对企业网络中的网元设备(包括交换机,路由器,防火墙等),很多企业希望根据自身的业务特点定制网络管理,比如可以实现网 ...
- 基于TCP协议的网络通讯流程
不多说了,先上个图: 从上面的图中可以看出来,基于TCP协议进行通讯可以大致分成以下几个阶段: 1. 首先是在服务器端, TCP Sever调用socket(), bind(), listen()完成 ...
- 基于TCP协议的网络编程
TCP通信协议是一种可靠的传输层协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成虚拟网络链路.一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信.Java使用Socke ...
- UNIX网络编程——基于UDP协议的网络程序
一.下图是典型的UDP客户端/服务器通讯过程 下面依照通信流程,我们来实现一个UDP回射客户/服务器: #include <sys/types.h> #include <sys/so ...
随机推荐
- C#文字转换语音朗读或保存MP3、WAV等格式
最近遇到一个需求,需要把文字转换语音,参考很多大佬写的方法,最后经过自己改造实现文字在线朗读.保存MP3.WAV等格式. //需要引用System.Speech程序集 //引用using System ...
- 关于.NET Core 2.0.2升级到2.1.1版本相关问题
之前,因日常任务管理比较混乱,所以自己开发了PTager任务管理系统. 当时用了.NET Core 2.0版本. 现在想修改相关功能,但.NET Core已发布到2.1.301了,也即2.1.1. 附 ...
- Fiddler4无法抓取HttpWebRequest本地请求的解决办法
网上很多解决案例是如下方代码设置代理,但在我的Fiddler4环境下无效,后寻得官方处理方法证实与代理无关. HttpWebRequest request= WebRequest.Create(&qu ...
- Java面试题(全)--视频系列
此系列为面试笔试题的视频讲解,以下均为超链接,点击即可进入每个知识点的讲解. Java面试题01.面试的整体流程 Java面试题02.java的垮平台原理 Java面试题03.搭建一个java的开发环 ...
- “全栈2019”Java异常第二十二章:try-with-resources语句详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...
- Struts2、SpringMVC、Servlet(Jsp)性能对比 测试
Struts2.SpringMVC.Servlet(Jsp)性能对比 测试 . Servlet的性能应该是最好的,可以做为参考基准,其它测试都要向它看齐,参照它. 做为一个程序员,对于各个框架的性能要 ...
- iphone 自学常用网址
https://www.gitbook.com/book/numbbbbb/-the-swift-programming-language-/details https://github.com/ip ...
- 如何在CentOS 7上使用vsftpd设置ftp服务器
一.前言介绍 FTP(文件传输协议)是一种标准的客户机-服务器网络协议,允许用户在远程网络之间传输文件. 有几个开源的FTP服务器可用于Linux.最受欢迎和广泛使用的是pureftpd.proftp ...
- Solr7.4的学习与使用
学习的原因: 17年的时候有学习使用过lucene和solr,但是后来也遗忘了,最近公司有个项目需要使用到全文检索,正好也顺便跟着学习一下,使用的版本是Solr7.4的,下载地址:http://arc ...
- 哈弗曼树的理解和实现(Java)
哈弗曼树概述 哈弗曼树又称最优树,是一种带权路径长度最短的树,在实际中有广泛的用途.哈弗曼树的定义,涉及路径.路径长度.权等概念.哈弗曼树可以用于哈弗曼编码,用于压缩,用于密码学等. 哈弗曼树的一些定 ...