实现基于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 ...
随机推荐
- Appium之打开应用时提示框处理
当打开一个应用时,会有一个无关紧要的提示框,如果要继续操作,需要先关闭提示框,如下图(如新用户福利提示): 此时,如果你直接用Appium inspector或者Android uiautomator ...
- [转]WCF体系结构-一张图就是好
本文转自:http://www.cnblogs.com/snakevash/archive/2011/05/02/2034414.html 今天在MSDN上面看到了这么一张图,让我顿时感觉脑袋清醒很多 ...
- Ubuntu的常识使用了解
1 在分区的时候也是有一定的机巧的,根据磁盘的特点,我们知道越是靠磁盘外部的柱面,旋转越快,而且每次旋转时,磁盘读写头可以覆盖较多的区域,也就意味着靠外部的柱面可以得到较好的性能.所以在分区时,我们应 ...
- FNDLOAD Commands to Download Different Seed Data Types. (DOC ID 274667.1)
In this Document Goal Solution References Applies to: Oracle Application Object Library - Version 11 ...
- 饿了么 PostgreSQL 优化之旅
1. 架构演变 在O2O外卖领域,基于位置服务的需求越来越多,这就要求DB能够存储地理位置信息,而在开源数据库中,对空间地理数据支持比较好的要数PG的插件Postgis. 饿了么在使用PG的过程中,由 ...
- yum 下载RPM包而不进行安装
yum命令本身就可以用来下载一个RPM包,标准的yum命令提供了--downloadonly(只下载)的选项来达到这个目的. $ sudo yum install --downloadonly < ...
- Cookie背景了解
Cookie的复数形态是Cookies, 英文的意思是小甜饼,小饼干. 类型为小型文本文件, 指某些网站为了辨别用户身份储存在用户本地中断上的数据. 是前网景公司的员工 卢-蒙特利在1993年3月发明 ...
- 弹性盒子模型display:flex(2)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- “全栈2019”Java异常第七章:try-catch-finally组合方式
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...
- [总结帖]Web小白的基础恶补帖
1. jQuery实现按钮点击跳转网页 <script src="js/jquery/jQuery-2.2.0.min.js" type="text/javascr ...