随着信息技术的发展,时间对人们越来越重要了,正所谓“效率就是金钱,时间就是生命”。不过,本文并不打算介绍如何安排时间,而是要介绍合理利用时间的基础——时间校正。用过一段时间电脑的人应该知道,电脑上的时钟往往是不准确的,而且经常是调整以后不久就乱了。这种误差是由CMOS内部的计时电路引起的。既然我们无法从硬件上杜绝这个问题,我们就要想办法从软件上弥补。
首先介绍一下网络对表的原理。网络上存在很多叫做“授时服务器”的主机,它们负责提供精确的时间,一般是由原子钟产生的。因此,我们只要做一个小程序来与授时服务器通信并按照其返回的准确时间来调整本机时间就可以了。
其中RFC组织为网络时间协议规定了几个不同版本:
DayTime协议:这是以ASCII字符传递时间信息的一种协议。参见RFC0867。
Time协议:这是以32位整数传递时间信息的一种协议。参见RFC0868。
Simple Network Time协议:这是一种更加精确的传递时间信息的协议。参见RFC1769。
你可以在http://rfc.linuxforum.net/找到相关RFC协议。
这几个协议各有优点,DayTime协议最简单,最直观,但规范不统一,只适合人脑阅读;Time协议也非常简单,但更适合用程序来统一处理;Simple Network Time协议比较复杂,但精度非常高。下面我将主要介绍一下Time协议的实现与应用。
Time协议中与授时服务器的通信是非常简单的:
其中“连接”是指连接到服务器的37端口(TCP或UDP方式均可,这里我们将选用TCP方式)。其中“时间信息”是一个4字节长的整数,定义如下:它表示自从GMT 0时区1900年1月1日午夜00:00开始到现在所经过的秒数。不过这个时间是以本初子午线处的时间为标准的。为了获得本地时间,我们还要按时区进行修正。例如,中国处于东八区,所以要把获得的时间加上8小时。当时间信息返回后,服务器将主动关闭这个连接。
下面我们就用C#实现这个程序了。新建一个叫TimeSync的C# Windows应用程序工程。其中Form1改名为frmMain。在frmMain代码前插入:
using System.Threading; //需要使用多线程
using System.Net; //需要使用网络
using System.Net.Sockets; //需要使用网络
//需要调用API来设置系统时间。
//笔者曾经想通过C#的类库来修改系统时间,但一直没有实现。
//希望熟悉这方面的朋友指点一下。
using System.Runtime.InteropServices;
class Win32API
{
[DllImport("kernel32.dll")]
public static extern int SetLocalTime (ref SystemTime lpSystemTime);
}
public struct SystemTime
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
主代码:
public class frmMain : System.Windows.Forms.Form
{
////省略自动生成的代码
////……………
Thread thrTimeSync;
private void TimeSyncProc()
{
while(true)
{
TcpClient c = new TcpClient();
c.Connect("www.time.ac.cn", 37); //连接到国内授时服务器
NetworkStream s;
s = c.GetStream(); //读取数据流
c.Close();
byte []buf = new byte[4];
s.Read(buf,0,4); //把数据存到数组中
uint lTime;
//把服务器返回数据转换成1900/1/1 UTC 到现在所经过的秒数
lTime = ((uint)buf[0] << 24) + ((uint)buf[1] << 16) + ((uint)buf[2] << 8) + (uint)buf[3];
//得到真实的本地时间
System.DateTime datetime = new DateTime(1900,1,1,0,0,0,0);
datetime = datetime.AddSeconds(lTime).ToLocalTime();
//这里可以显示出时间。有兴趣的朋友可以取消注释看一下效果。
//MessageBox.Show(datetime.ToLongDateString() + datetime.ToLongTimeString());
//修改系统时间
SystemTime sysDateTime = new SystemTime();
sysDateTime.wYear = (short)datetime.Year;
sysDateTime.wDay = (short)datetime.Day;
sysDateTime.wMonth = (short)datetime.Month;
sysDateTime.wHour = (short)datetime.Hour;
sysDateTime.wMinute = (short)datetime.Minute;
sysDateTime.wSecond = (short)datetime.Second;
Win32API.SetLocalTime(ref sysDateTime);
System.Threading.Thread.Sleep(1 * 1000);
}
}
private void frmMain_Load(object sender, System.EventArgs e)
{
thrTimeSync = new Thread(new ThreadStart(TimeSyncProc));
thrTimeSync.Start();
}
private void frmMain_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
thrTimeSync.Abort();
}
}
到此为止,本文的内容基本讲完了。但有的朋友或许会问,这种方法的精确度如何保证?网络速度难道不会造成误差吗?没错,这正是本文所讲方法的局限性。使用这种方法,由于网络延迟带来的误差是难以消除的,因此只能用在要求不高的场所。在对精度要求很高的情况中,可以使用Simple Network Time协议(RFC1769, http://rfc.linuxforum.net/rfc1769.txt)来精确授时。其大体原理是数据包在经过服务器时都记录时间损失,最终通过调整来达到最小误差。
源程序可以在 这里 下载。