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

网络下载限制器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (37投票s)

2010年7月4日

CDDL

6分钟阅读

viewsIcon

70270

downloadIcon

1632

如何设置下载限制并禁用用户网络连接。

引言

对于那些拥有流量限制的宽带套餐并育有子女的家庭来说,这篇文章可能会是救星,它展示了一种通过设定每日下载额度来限制用户下载能力的方法。

背景

于是,我正对我家孩子因为 ISP 再次收取过高的下载费用而喋喋不休时,我突然意识到我需要一种方法来限制他们的下载容量。在搜索网络并在 CP 论坛上提问后,我有了这个小程序的主意。

图像

实际运行的程序会在用户桌面上创建一个永久的、非常小的窗口,告知他们还剩余多少下载容量。

Capture2.PNG

一旦达到限制,程序会告知用户他们的限制已达,并且他们的互联网访问已被禁用。

Capture1.PNG

Using the Code

我尽量让程序尽可能简单,因此代码本身的操作方式也相对简单。我们使用性能计数器(有关更多信息,请参阅 http://msdn.microsoft.com/en-us/library/system.diagnostics.performancecounter(VS.90).aspx)来收集网络适配器接收到的字节数信息。

' Instantiate our performance counter that will get the information we need.
downloadCounter = New PerformanceCounter("Network Interface", "Bytes Received/sec", nicName)

然后我们进行累计,如果累计总数超过预定限制,我们将禁用用户的网络连接,直到第二天。就是这么简单!

那么,我们如何实现这一切呢?我猜您正在问,我们如何禁用用户的互联网连接?很简单!我们停止用户计算机上的 DHCP 服务。现在,这里有个问题:如果您不使用 DHCP 服务器动态分配的 IP 地址,停止客户端的 DHCP 服务将不会有任何区别;它不会禁用您的连接 :-(

Private Sub StopService(ByVal service As String)
    'http://msdn.microsoft.com/en-us/library/aa288037%28VS.71%29.aspx

    Dim sc As ServiceController = New ServiceController(service)

    If sc.CanStop And sc.Status <> ServiceControllerStatus.Stopped Then
        sc.Stop()
        sc.WaitForStatus(ServiceControllerStatus.Stopped)
    End If
End Sub

为了能够收集下载的字节数并进行累计,我们创建一个每秒触发一次的事件,并更新最新的下载总数。为了创建这个触发器,我们创建了一个一秒钟的计时器。

' Set the time interval for collecting the download data (milliseconds).
Dim myTimer As System.Timers.Timer = New System.Timers.Timer(1000)

然后我们创建一个事件处理程序,并将其指向我们的委托集合子程序。

AddHandler myTimer.Elapsed, New ElapsedEventHandler(AddressOf timer_Elapsed)

这是我们获取更新的地方。

Private Sub timer_Elapsed(ByVal sender As Object, ByVal e As ElapsedEventArgs)

    downloadBytes = downloadCounter.NextSample().RawValue
    regKey.SetValue("PreviouslyDownloadedBytes", downloadBytes, RegistryValueKind.DWord)

End Sub

注意:我们还将累计总数保存到注册表中。这将在后面解释。

我们还使用 timer.tick 事件来查看是否已达到我们的下载限制。

Private Sub TimerCounter_Tick(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles TimerCounter.Tick
    If Not disabled Then
        lblDownloadBytes.Text = ((tempDownloadLimit - downloadBytes) / 
             (1024 ^ 2)).ToString("###0.###") & " MBytes remaining"

        If downloadBytes > tempDownloadLimit Then

            ' We've reached our download limit.
            regKey.SetValue("Expired", True, RegistryValueKind.DWord)
            regKey.SetValue("PreviouslyDownloadedBytes", 0, RegistryValueKind.DWord)

            disabled = True

            StopService(service)
            lblDownloadBytes.Text = "Download Limit Reached, Internet Access Disabled."
        End If
    Else

        ' If we've reached the download limit, we stop the timer and remove the event handler
        ' that monitors the downloaded bytes.
        ' No point in monitoring anymore something that isn't happening! 
        ' We then rely on the AddressChanged sub to continue disabling the network should the
        ' user restart the dhcp service.
        TimerCounter.Stop()
        RemoveHandler myTimer.Elapsed, AddressOf timer_Elapsed

    End If
End Sub

就程序目前的状态而言,按所述方式,很容易规避并恢复网络访问。因此,我们加入了一些检查来帮助阻止规避。我们执行的第一个检查之一是查看计算机是否已重启,从而重置我们的性能计数器。

从上一个代码块可以看出,我们设置了几个注册表项。其中一个条目是“Expired”。这是一个布尔值,如果为 true,则表示下载限制已达到。我们在启动时检查是否“已过期”,如果已过期,我们会立即禁用 DHCP 服务。

我们保存的另一个注册表设置是“PreviouslyDownloadedBytes”。这用于用户重启计算机但当天未用完下载限制的情况。我们将 PreviouslyDownloadedBytes 的值减去我们设置的限制,这成为当天剩余时间的新下载限制。

Case DateComparisonResult.TheSame
' We're running again on the same day. Check to make sure we've not expired.

If downloadLimitReached Then
    lblDownloadBytes.Text = "Download Limit Reached, Internet Access Disabled."
    disabled = True
    StopService(service)
Else
    ' Adjust the download limit in case the computer has been restarted.
    tempDownloadLimit = maxDownloadLimit - previouslyDownloadedBytes
End If

另一个执行的检查是查看用户是否将系统时钟向前移动,从而重置下载计数器。我们在注册表中保存上次运行的日期,然后在启动时将此日期与系统的当前日期进行比较,以确定是否应授予用户互联网访问权限。为了准确确定系统时钟是否正确,我们查询 NTP 时间服务器。DaveyM69 创建了一个非常详细且有用的 CP 文章,关于查询时间服务器,请参阅:https://codeproject.org.cn/KB/datetime/SNTPClient.aspx 以获取更多信息。我修改了 Dave 的原始 SNTPClient 代码以满足我的需求,并使用此代码查询时间服务器。

' First check to see if the dhcp service is running, if not, start it.
' We need network access to communicate with an NTP server to check that 
' the system clock hasn't been changed.
If Not IsServiceRunning(service) Then
    StartService(service)
End If

' Make sure the nic is connected to the network before querying
' the NTP server.
If My.Computer.Network.IsAvailable Then
    ntpServerDate = SntpClient.GetNow().ToShortDateString
End If

' Check whether the system clock has been changed.
If currentDate <> ntpServerDate Then
    ' System clock has been changed! Reset it.
    SntpClient.UpdateLocalDateTime = True
    SntpClient.GetNow()
End If

我们做的最后一次检查是监控分配给 NIC 的 IP 地址的状态。如果我们禁用了 DHCP 服务从而禁用了连接,NIC 的 IP 地址将不存在。我们设置了一个事件处理程序,在 NIC 的 IP 地址更改时通知我们。

AddHandler NetworkChange.NetworkAddressChanged, AddressOf AddressChanged

然后,我们使用我们的委托子程序在用户重新启用 DHCP 服务时再次禁用它。

Private Sub AddressChanged(ByVal sender As Object, ByVal e As EventArgs)

    ' We're notified here of an IP address change. If the network should have been
    ' disabled and we reach here, it means the user has restarted the dhcp service. 
    ' So we stop it again.
    If disabled Then
        StopService(service)
    End If

End Sub

那么,各位女士们先生们,这就是如何在一个预定的字节数下载后简单地禁用网络连接。

关注点

因为性能计数器的实例化需要 NIC 的名称,所以我们使用 WMI 例程来获取 NIC 的名称。

Private Function GetNetworkAdaptorName() As String

        ' Get the name of the nic that has an IP address associated with it.

        Dim nicName As String = ""

        Dim query As ManagementObjectSearcher = New ManagementObjectSearcher _
                            ("SELECT * FROM Win32_NetworkAdapterConfiguration " + 
                             "WHERE IPEnabled = TRUE")

        Dim queryCollection As ManagementObjectCollection = query.Get()
        Dim dhcpAddress() As String

        For Each mo As ManagementObject In queryCollection
            nicName = mo("Description").ToString.Trim
            dhcpAddress = CType(mo("IPAddress"), String())

            If dhcpAddress(0) <> "" Or dhcpAddress(0) <> "255.255.255.255" Then
                ' Replace any forward slashes to underscores.
                ' Nvidia adaptors often have forward slashes
                ' in their NIC names which totally screws things up.
                If nicName.Contains("/"c) Then
                    nicName = nicName.Replace("/"c, "_"c)
                End If
                Exit For
            End If
        Next
        Return nicName
End Function

尽管我们尝试使用

WHERE IPEnabled = TRUE 

子句过滤 WMI 查询返回的 NIC 数量,但有时可能会返回多个 NIC。在这种情况下,我们检查返回的 NIC 是否与其关联有 DHCP 服务器地址。

我们在程序中还使用了一个启动 DHCP 服务的例程。我发现的一个问题是,如果您启动 DHCP 服务然后立即尝试使用任何网络功能,该功能将失败,即使我们使用

sc.WaitForStatus(ServiceControllerStatus.Running)

显式等待服务启动。这是因为,即使服务正在运行,DHCP 服务器也需要时间来为 NIC 分配 IP 地址。为了保持简单,我只是在任何网络功能使用之前添加了 5 秒的延迟。

Private Sub StartService(ByVal service As String)

    Dim sc As ServiceController = New ServiceController(service)

    If sc.Status <> ServiceControllerStatus.Running Then
        Try
            sc.Start()
            sc.WaitForStatus(ServiceControllerStatus.Running)

            ' Even though we wait for the status of the dhcp service to change to
            ' Running, we have to pause to give the dhcp server time to allocate 
            ' an IP address to our Nic. 
            Thread.Sleep(5000)

        Catch ex As Exception
            lblDownloadBytes.Text = "Could Not Start DHCP Service"
        End Try
    End If

End Sub

运行程序

要在用户系统上运行该程序,请将可执行文件放在一个不显眼的地方,例如“Windows\system32”,然后创建一个注册表项,在 HKLM\Software\Microsoft\Windows\CurrentVersion\Run 注册表键下启动该程序。

注意事项

虽然该程序在 Windows XP 和 Windows 7 上运行良好,但用户必须是管理员组的成员。此外,在 Vista 上,即使 UAC 没有禁用,在启动和停止 DHCP 服务以及尝试更改系统时间时也会出现 UAC 提示。

待办事项

我认为,要克服以管理员身份运行和 UAC 问题,所有不向用户显示信息的内容都应该成为一个 Windows 服务,并有一个单独的 GUI 应用程序与该服务通信,以便向用户显示他们当前的下载使用情况。

还有一件事:如果用户在任务管理器中停止了我们的应用程序,显然我们就无法监控下载使用情况。我一直在考虑在窗体关闭事件中添加一个例程,如果在启动时未运行该应用程序,则会关闭用户计算机。此外,将我们的应用程序集成到这样一种方式中,即如果在启动时未运行,NIC 本身就无法启动,这可能是一个好主意,例如通过依赖我们的应用程序来启动 NIC 的驱动程序。

历史

  • 2010 年 7 月 4 日 - 初始版本。
  • 2010 年 7 月 5 日 - 错误修复。
© . All rights reserved.