网络下载限制器
如何设置下载限制并禁用用户网络连接。
引言
对于那些拥有流量限制的宽带套餐并育有子女的家庭来说,这篇文章可能会是救星,它展示了一种通过设定每日下载额度来限制用户下载能力的方法。
背景
于是,我正对我家孩子因为 ISP 再次收取过高的下载费用而喋喋不休时,我突然意识到我需要一种方法来限制他们的下载容量。在搜索网络并在 CP 论坛上提问后,我有了这个小程序的主意。
图像
实际运行的程序会在用户桌面上创建一个永久的、非常小的窗口,告知他们还剩余多少下载容量。
一旦达到限制,程序会告知用户他们的限制已达,并且他们的互联网访问已被禁用。
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 日 - 错误修复。