实现基于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 ...
随机推荐
- word复制粘贴表格不齐
1.查找橡皮擦 2.有时候复制粘贴 表格 会将以前的东西格式也粘贴进来,需要清除格式和重新排版 3.word2007清除格式
- ZSTU4269 买iphone 2017-03-22 14:31 73人阅读 评论(0) 收藏
4269: 买iphone Time Limit: 3 Sec Memory Limit: 128 MB Submit: 1710 Solved: 316 Description 自从上次仓鼠中了 ...
- IntentService介绍
1.IntentService 是什么 一个封装了HandlerThread和Handler的异步框架. 是一种特殊Service,继承自Service,是抽象类,必须创建子类才可以使用. 可用于执行 ...
- C#递归例程
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Cons ...
- codeforces 1096 题解
A: 发现最优的方案一定是选 $ l $ 和 $ 2 * l $,题目保证有解,直接输出即可 #include <bits/stdc++.h> #define Fast_cin ios:: ...
- robot framework学习笔记之二———变量
Robot Framework的变量分为标量, 列表和字典, 分别使用语法格式 ${SCALAR}, @{LIST} 和 &{DICT} 来定义. 此外, 环境变量可以直接使用语法 %{ENV ...
- PHP性能优化四(业务逻辑中使用场景)
php脚本性能,很多时候依赖于你的php版本.你的web server环境和你的代码的复杂度. Hoare曾经说过“过早优化是一切不幸的根源”.当你想要让你的网站更快运转的时候,你才应该去做优化的事情 ...
- Utils工具方法集插件详解
var Utils = function(){}; Utils.text = { stripTags: function (val) { return val.replace(/<\/?[^&g ...
- 为服务器设置固定IP地址
为服务器设置固定IP地址 1.获取超级管理员权限 命令:$ su - 输入root密码 2.判断哪个网卡有流量,或者确定需要设置哪个网卡的固定ip 命令:# ifconfig PS:可以查询哪些网卡有 ...
- 零基础学习Python数据分析
网上虽然有很多Python学习的教程,但是大多是围绕Python网页开发等展开.数据分析所需要的Python技能和网页开发等差别非常大,本人就是浪费了很多时间来看这些博客.书籍.所以就有了本文,希望能 ...