65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 SNTP 进行日期验证

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (8投票s)

2009年7月8日

CPOL

4分钟阅读

viewsIcon

34223

downloadIcon

489

检查您的应用程序的到期日期是否已过,而不考虑系统日期。

概述

DateValidator是一个.NET组件,它将给定日期与从时间服务器检索到的日期进行比较。我最近需要制作一个有时限的应用程序版本。在这种特定情况下,应用程序的到期日期不需要灵活,因此被硬编码到应用程序中。仅检查系统日期是不够的,因为它可以很容易地更改,所以显而易见的方法是从另一个来源获取“真实”日期。本文是我提出的一个快速解决方案。

这不是一个用于保护您的有时限应用程序或NTP/SNTP的全面解决方案,但它可以作为一个良好的起点。

SNTP

世界各地有许多时间服务器,并且有几种不同的协议在使用。此组件使用了简单网络时间协议(SNTP)的一小部分。SNTP通过在UDP套接字123端口上发送/接收48(或更多)字节的数据包工作。在本组件中(由于所需的精度较低),我们只关心其中5个字节(0和40-43)。客户端连接到服务器并发送一个请求包,然后接收一个包含数据的响应包。实际的日期/时间不在数据中!相反,一个包含自1900年1月1日00:00:00 UTC以来的秒数的 Timestamp 嵌入在字节40-43中(小数秒在字节44-47中)。通过这个,我们可以计算出当前的UTC并与我们的进行比较,以查看它是否有效。

为了实现完整的时间同步,还需要使用许多其他字节/时间戳来获得高精度并补偿发送/接收中的延迟。这对于本组件来说不是必需的,因为我们只需要到天的精度;因此这里不作介绍,但如果您需要,它很容易实现。

组件

注意:在验证过程中抛出的任何异常都会被捕获并传递给事件参数,而不是被重新抛出。您可以检查参数以确定要作为验证失败处理的异常。

该组件非常简单。它有一个方法(+3个重载)ValidateAsync和一个事件Validated。该方法接受一个DateTime作为应用程序的到期日期,以及可选的TimeServerTimeOut值。此方法启动一个BackgroundWorker,实际工作在那里完成(在一个单独的线程上)。完成后,将引发事件,并附带一个ValidatedEventArgs实例,其中包含我们所需的所有信息。

这是执行验证的代码(详细信息如下)

ValidatedEventArgs  validationArgs = 
           new ValidatedEventArgs(_TimeServer, _MaxDate);
UdpClient client = null;
try
{
    client = new UdpClient();
    IPEndPoint ipEndPoint = _TimeServer.GetIPEndPoint();
    client.Client.SendTimeout = _TimeOut * 1000;
    client.Client.ReceiveTimeout = _TimeOut * 1000;
    client.Connect(ipEndPoint);

    client.Send(Request, byteCount);
    byte[] received = client.Receive(ref ipEndPoint);

    if (received.Length >= byteCount && 
       ((received[0] & modeMask) == modeServer))
    {
        ulong transmitTimeStamp = 0;
        for (int i = 40; i <= 43; i++)
            transmitTimeStamp = (transmitTimeStamp << 8) | received[i];

        DateTime result = rootDateTime;
        result += TimeSpan.FromSeconds(transmitTimeStamp);
        validationArgs.TimeServerDate = result.Date;
        if (validationArgs.TimeServerDate <= _MaxDate)
        {
            validationArgs.ValidationResult = ValidationResult.OK;
        }
        else
        {
            validationArgs.ValidationResult = ValidationResult.Fail;
            validationArgs.ErrorText = "This software has expired.";
        }
    }
    else
    {
        validationArgs.ValidationResult = ValidationResult.Invalid;
        validationArgs.ErrorText = "Invalid result from TimeServer.";
    }
}
catch (Exception ex)
{
    validationArgs.ValidationResult = ValidationResult.Exception;
    validationArgs.ErrorText = ex.Message;
    validationArgs.Exception = ex;
}
finally
{
    // clean up and set the result to our args.
    if(client!=null)
        client.Close();
    e.Result = validationArgs;
}

首先,我们创建、初始化和连接UDP套接字。然后,我们发送请求包。这是一个48字节的数组。字节[0]的位0、1和2指示模式。我们是一个客户端,由值3(011)表示。字节[0]的位3、4和5指示我们使用的SNTP版本。这是使用版本4,所以我们将'4'(100)左移三位,并将其与模式进行OR运算,得到字节[0](00100011)。我们也可以直接将字节[0]设置为35,但我们是按照协议的逻辑正确进行的,这样在协议发生变化时,代码会更容易重构。

private const int byteCount = 48;
private const byte sntpVersion = 4;
private const int versionBitOffset = 3;
private const byte modeMask = 7;
private const byte modeClient = 3;
private const byte modeServer = 4;
private static byte[] Request
{
    get
    {
        byte[] result = new byte[byteCount];
        result[0] = modeClient | (sntpVersion << versionBitOffset);
        return result;
    }
}

接下来,我们等待从服务器接收响应。一旦我们收到字节数组,我们就检查长度和模式位,以确保数据有效并且来自服务器。然后,我们遍历时间戳的整数部分(字节40-43)以获取秒数,并将其转换为DateTime(UTC)。如果过程中有任何错误或异常,它们会相应地放置在ValidatedEventArgs的一个实例中。最后,我们关闭套接字。

此时,将调用BackgroundWorkerRunWorkerCompleted方法,在那里我们检索事件参数并引发事件;现在,我们回到了原始线程。

backgroundWorker = null;
ValidatedEventArgs result = e.Result as ValidatedEventArgs;
OnValidated(result);

其他类

TimeServer

这是一个简单的类,用于保存服务器的主机名和端口。为了方便起见,我在其中放置了几个静态TimeServer(有些分组在数组中)。

ValidatedEventArgs

一个简单的类,派生自System.EventArgs,用于保存有关验证的数据。

有用参考

历史

  • 2009年7月8日:初始版本。
© . All rights reserved.