在本文中,将探讨一个.NET类库,它能够检测网络的可用性并监听网络连接的变化。关注的重点是“互联网可用性”,即如果至少有一个与IP相关的接口可用并且正常工作,那么认为“网络”是可用的。
iTuner歌词引擎依赖于在线提供商来搜索歌词。每当播放一首尚未有歌词的歌曲时,它就会连接到互联网。iTuner的具体需求如下:
这导致了对System.Net.NetworkInformation命名空间的使用,以及下面展示的薄包装器。这些System类没有覆盖的一些场景,本文将进行讨论。当然,可以将这些需求泛化到任何应用程序,或者通过添加其他功能(如在进行更重的通信之前ping目标站点以检查其存在)来扩展这个类。
NetworkStatus类是一个静态类,暴露了一个方法和一个事件,如下所示:
public static class NetworkStatus {
public static event NetworkStatusChangedHandler AvailabilityChanged;
public static bool IsAvailable { get; }
}
如所见,这个类并没有太多内容——这正是重点。尽管这个类背后的代码量不大,但它封装了所有的复杂性,所以不需要担心细节。本文附带的NetworkStatusDemo应用程序展示了这个类最基础的用法。它首先使用IsAvailable属性报告当前的网络可用性。
if (NetworkStatus.IsAvailable) {
Console.WriteLine("... Network is available");
} else {
Console.WriteLine("... Network is not available");
}
然后,NetworkStatusDemo将一个NetworkStatusChangedHandler处理程序附加到AvailabilityChanged事件。
NetworkStatus.AvailabilityChanged += new NetworkStatusChangedHandler(DoAvailabilityChanged);
static void DoAvailabilityChanged(object sender, NetworkStatusChangedArgs e) {
if (e.IsAvailable) {
Console.WriteLine("... Network is available");
} else {
Console.WriteLine("... Network is not available");
}
}
非常简单,但这足以确定在任何特定时间点是否应该尝试联系网站或Web服务。
虽然.NET类提供了这里所需的大部分功能,但有两个主要目标:
System.Net.NetworkInformation.NetworkInterface类包括一个静态GetIsNetworkAvailable()方法。虽然这个方法已经做了需要的大部分工作,但发现在某些情况下,尤其是无线适配器,网络看起来是在线的,但还没有真正建立到网络的开放连接。它还会看到虚拟机适配器并认为它们是在线的;显然,除非有一些奇怪的开发配置,其中使用虚拟机作为路由器,否则这些不会导致连接到互联网。
首先使用GetIsNetworkAvailable()进行快速测试,然后通过检查每个适当的网络接口的活动、BytesReceived和BytesSent来扩展这一点。这在NetworkStatus.IsNetworkAvailable()方法中进行了封装。
private static bool IsNetworkAvailable() {
if (NetworkInterface.GetIsNetworkAvailable()) {
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface face in interfaces) {
if (face.OperationalStatus == OperationalStatus.Up) {
if ((face.NetworkInterfaceType != NetworkInterfaceType.Tunnel) &&
(face.NetworkInterfaceType != NetworkInterfaceType.Loopback)) {
IPv4InterfaceStatistics statistics = face.GetIPv4Statistics();
if ((statistics.BytesReceived > 0) &&
(statistics.BytesSent > 0)) {
return true;
}
}
}
}
}
return false;
}
虽然这足以测试当前的可用性,但可以通过维护一个私有布尔变量isAvailable,并仅在连接状态变化时设置其状态来优化效率。这就是NetworkChange类发挥作用的地方。
System.Net.NetworkInformation.NetworkChange类有两个事件,NetworkAvailabilityChanged和NetworkAddressChanged。听起来不错。但问题是NetworkChange不区分可能不让访问互联网的适配器,如隧道适配器。因此,NetworkStatus类扩展了这个功能,只包括相关适配器的事件。通过紧密控制NetworkAvailabilityChanged和NetworkAddressChanged事件自己来管理这一点,将它们隐藏在自己的AvailabilityChanged事件后面。
public static event NetworkStatusChangedHandler AvailabilityChanged {
add {
if (handler == null) {
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(DoNetworkAvailabilityChanged);
NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(DoNetworkAddressChanged);
}
handler = (NetworkStatusChangedHandler)Delegate.Combine(handler, value);
}
remove {
handler = (NetworkStatusChangedHandler)Delegate.Remove(handler, value);
if (handler == null) {
NetworkChange.NetworkAvailabilityChanged -= new NetworkAvailabilityChangedEventHandler(DoNetworkAvailabilityChanged);
NetworkChange.NetworkAddressChanged -= new NetworkAddressChangedEventHandler(DoNetworkAddressChanged);
}
}
}
可以看到提供了自己的handler变量。这被定义为想要在状态变化时向消费者发出信号的事件。不幸的是,两个包装事件的处理程序没有类似的签名。所以需要提供两个不同的处理程序。但它们都会流入一个单一的点,SignalAvailabilityChange。
private static void SignalAvailabilityChange(object sender) {
bool change = IsNetworkAvailable();
if (change != isAvailable) {
isAvailable = change;
if (handler != null) {
handler(sender, new NetworkStatusChangedArgs(isAvailable));
}
}
}