完整源码,附工程下载,工程其实也就下面两个代码。
想在不能上网的服务器局域网中部署一个时间服务NTP,当然系统自带该服务,可以开启,本文只是分享一下该协议报文和能跑的源码。网上作为服务的源码不太常见,能见到不一定能跑起来,缺胳膊少脚的,分享代码一定要分享全。
先来效果图:
开启程序后,让win系统来同步本机时间效果如下:
用通用客户端GuerrillaNtp来读,也没有问题:
添加图片注释,不超过 140 字(可选)
Fuck Shut Up,Show me the code
源码如下:
运行入口:
namespace NTP
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, NTP(SNTP)!");
Console.WriteLine("本程序占用 UDP 123 端口,如遇冲突,请关闭系统自带时间服务 W32Time(Windows Time)");
new SNTPServer().Start();
Console.ReadLine();
}
}
}
主程序代码:
using System.Net.Sockets;
using System.Net;
using System.Buffers.Binary;
namespace NTP
{
/// <summary>
/// 简单网络时间协议服务器
/// </summary>
public class SNTPServer
{
int port = 123; //服务端口,NTP默认端口123
bool stopFlag = false; //通知后台线程停止消息循环的标识
Thread tdServer; //服务器后台监听线程
/// <summary>
/// 初始化一个简单网络时间协议服务器
/// </summary>
public SNTPServer()
: this(123) { }
/// <summary>
/// 使用指定参数初始化一个简单网络时间协议服务器
/// </summary>
/// <param name="port">服务端口</param>
public SNTPServer(int port)
{
this.port = port;
}
/// <summary>
/// 获取和设置服务端口号
/// </summary>
public int Port
{
get { return this.port; }
set { this.port = value; }
}
/// <summary>
/// 启动服务器
/// </summary>
public void Start()
{
if (tdServer == null || (!tdServer.IsAlive))
{
tdServer = new Thread(bgWork);
tdServer.IsBackground = true;
stopFlag = false;
tdServer.Start();
}
}
/// <summary>
/// 停止服务器
/// </summary>
public void Stop()
{
stopFlag = true;
}
private void bgWork()
{
IPEndPoint iep;
UdpClient udpclient;
try
{
iep = new IPEndPoint(IPAddress.Any, port);
udpclient = new UdpClient(iep);
while (!stopFlag)
{
if (udpclient.Available > 0)
{
Console.WriteLine("收到一个请求:" + iep.Address + " 端口:" + iep.Port);
byte[] buffer;
IPEndPoint remoteipEP = new IPEndPoint(IPAddress.Any, port);
buffer = udpclient.Receive(ref remoteipEP);
if (buffer.Length >= 48)
{
var bytesReceive = buffer.AsSpan();
Console.WriteLine("收到数据[" + buffer.Length + "]:" + BitConverter.ToString(buffer));
//记录收到请求的时间
DateTime ReceiveTimestamp = DateTime.Now;
//对方请求发出的时间,拿来显示看看
DateTime? OriginateTimestamp = Decode(BinaryPrimitives.ReadInt64BigEndian(bytesReceive[40..]));
//传输的都是国际时间,这里显示方便转成北京时间
Console.WriteLine("对方请求发出时间:" + OriginateTimestamp?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff"));
var Mode = 4;
var VersionNumber = 3;
var LeapIndicator = 0;
var Stratum = 4;//Stratum 0是最高层级,代表原子钟、GPS接收器或其他高精度的时间源。
var Poll = 0x0A;//NTP客户端向远程服务器发送时间请求的间隔(以秒为单位)。
var Precision = 0xE9;
buffer = new byte[48];
var bytes = buffer.AsSpan();
//LI(Leap Indicator):长度为2比特,值为“11”时表示告警状态,时钟未被同步。为其他值时NTP本身不做处理。
//VN(Version Number):长度为3比特,表示NTP的版本号,目前的最新版本为3。
//Mode:长度为3比特,表示NTP的工作模式。不同的值所表示的含义分别是:0未定义、1表示主动对等体模式、2表示被动对等体模式、3表示客户模式、4表示服务器模式、5表示广播模式或组播模式、6表示此报文为NTP控制报文、7预留给内部使用。
//0x1C = 00 011 100
bytes[0] = (byte)(((uint)LeapIndicator << 6) | ((uint)VersionNumber << 3) | (uint)Mode);
bytes[1] = (byte)Stratum;
bytes[2] = (byte)Poll;
bytes[3] = (byte)Precision;
BinaryPrimitives.WriteInt32BigEndian(bytes[4..], Encode(TimeSpan.Zero));//RootDelay = 0; Root Delay是指从主参考时钟到NTP服务器之间的往返时间延迟。它表示从主参考时钟到NTP服务器再返回的总体时间,通常以毫秒为单位。
BinaryPrimitives.WriteInt32BigEndian(bytes[8..], Encode(TimeSpan.Zero));//RootDispersion = 0; Root Dispersion是指本地时钟相对于主参考时钟的最大误差。它表示NTP服务器时钟与主参考时钟之间的最大时间偏差,通常以毫秒为单位。
BinaryPrimitives.WriteUInt32BigEndian(bytes[12..], BitConverter.ToUInt32(new byte[] { 0x41, 0x43, 0x54, 0x53 }, 0));//ReferenceIdentifier
BinaryPrimitives.WriteInt64BigEndian(bytes[16..], Encode(DateTime.Now.ToUniversalTime()));//ReferenceTimestamp 系统时钟最后一次被设定或更新的时间
//对方请求发出的时间 转成datetime 再转字节 会造成精度损失,【请求端会不认该报文】,应该返回原装字节, //BinaryPrimitives.WriteInt64BigEndian(bytes[24..], Encode(OriginateTimestamp));
bytes[24] = bytesReceive[40];
bytes[25] = bytesReceive[41];
bytes[26] = bytesReceive[42];
bytes[27] = bytesReceive[43];
bytes[28] = bytesReceive[44];
bytes[29] = bytesReceive[45];
bytes[30] = bytesReceive[46];
bytes[31] = bytesReceive[47];
BinaryPrimitives.WriteInt64BigEndian(bytes[32..], Encode(ReceiveTimestamp.ToUniversalTime()));//ReceiveTimestamp
BinaryPrimitives.WriteInt64BigEndian(bytes[40..], Encode(DateTime.Now.ToUniversalTime()));//TransmitTimestamp
Console.WriteLine("返回数据[" + buffer.Length + "]:" + BitConverter.ToString(buffer));
//系统NTP真实返回报文示例
//buffer = new byte[] { 0x1C,0x04,0x00,0xE9,0x00,0x00,0x21,0xAA,0x00,0x00,0x16,0x0C,0x34,0xE7,0x72,0xB7,0xEB,0x0E, 0x62, 0x72,0xF7,0xCF,0x33,0xAF,0xEB,0x0E, 0x66, 0x3E, 0x79, 0x8B,0xB0,0x00,0xEB,0x0E, 0x66, 0x3E, 0x97, 0xCE,0xE6,0x82,0xEB,0x0E, 0x66, 0x3E, 0x97, 0xCF,0x51,0xE2 };
udpclient.Send(buffer, buffer.Length, remoteipEP);
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private const double FACTOR = 1L << 32;
static readonly DateTime epoch = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc) + TimeSpan.FromSeconds(1L << 32);
static int Encode(TimeSpan time) => (int)(time.TotalSeconds * (1 << 16));
static long Encode(DateTime? time) => time == null ? 0 : Convert.ToInt64((time.Value - epoch).TotalSeconds * (1L << 32));
static DateTime? Decode(long bits) => bits == 0 ? null : epoch + TimeSpan.FromSeconds(bits / FACTOR);
}
}
本代码亲自测试,木有问题.
工程下载:download.csdn.net/download/wudizhukk/90159750