当前位置: 首页 > news >正文

.NET 4.0网络开发入门之旅-- 我在“网” 中央(下)

NET 4.0网络开发入门之旅--

我在“网” 中央(下)

续上篇《.NET 4.0网络开发入门之旅——我在“网” 中央(上) 》

4 我“连网”了吗?

好了,有了前面的铺垫,现在解决判断计算机连网问题水到渠成,其思路很简单:


(1)检测一下计算机中的所有网络接口状态,只要都为“Down”,没说的,肯定没连网。
(2)从状态为“Up”的网络接口中选一个(注意排除掉环回接口Loopback),获取其网关和DNS服务器地址,先Ping一下它的网关,如果能Ping通,再Ping一下DNS服务器,看看能不能Ping通。
如果网关Ping不通,就换一个“Up”的网络接口试一试,重复上述过程,直到检测完了所有“Up”的网络接口。

现在结果呼之欲出了:
(1)只要有一个网络接口可以Ping通网关,则“本计算机肯定已连上本地网络。”
(2)只要有一个网络接口可以Ping通DNS,则“本计算机的本地网络设置没有问题。”除非DNS服务器本身故障(应该发生机率不高)或由于你欠费之类的账号被限制,则“本计算机联上互联网应该没问题”。
(3)为了真正确认已联上互联网,Ping一个“Well-Known”的网址,比如“百度”,能Ping通则100%确信可以上网了。

注意:
某些网站不响应Ping数据包,比如笔者发现微软公司主机就不理会Ping数据包,对其主机的Ping操作将以“超时(TimeOut)”收场。

有的朋友看到这里,不禁会说:


你这不是多此一举了吗?一上来直接Ping一个互联网主机,不就知道能不能上网了吗?


呵呵,这怎么说呢?不能上网的原因太多了,如果你的网络应用程序能告之用户更详细的信息:

  • 无法连接网关,请检查你的网络设置
  • 无法连接DNS服务器,您所指定的DNS服务地址“192.168.1.1”可能有误,或者是DNS服务器故障……

是不是更有助于用户定位网络问题?
我在示例程序中写了一个IsOnline方法实现了前述的连网检测逻辑:

static bool IsOnline()
{
if (NetworkInterface.GetIsNetworkAvailable() == false )
return false ; // 所有网卡都是“Dwon”的
// 选出“Up”的网卡,并且排除掉环回接口
varquery = fromnic in NetworkInterface.GetAllNetworkInterfaces()
where nic.OperationalStatus == OperationalStatus.Up &&
nic.NetworkInterfaceType
!= NetworkInterfaceType.Loopback
selectnic;
foreach (varnic in query)
{
// 先Ping网关
Pingpinger = new Ping();
bool GatewayReady = false ;
foreach (GatewayIPAddressInformationGatewayAddr in
nic.GetIPProperties().GatewayAddresses)
{
PingReplyreply
= pinger.Send(GatewayAddr.Address);
if (reply.Status == IPStatus.Success)
{
GatewayReady
= true ;
break ;
}
}
if (GatewayReady == false )
continue ; // 所有网关都不通,不再进行下一步测试,直接检测下一个网卡
// 网关Ping通了,可以PingDNS
foreach (IPAddressaddr in nic.GetIPProperties().DnsAddresses)
{
PingReplyreply
= pinger.Send(addr);
if (reply.Status == IPStatus.Success)
{
return true ;
}
}
}
return false ;
}


上述代码中除了没有完成Ping一个“Well-Known”的真实互联网之外,其余的工作都已完成。


IsOnLine方法返回“True”时,表示本地网络可以Ping通DNS,否则,返回“False”,你可以修改这个方法,让其依据具体情况返回更详细的信息(比如是哪个网卡的网关还是DNS不能Ping通)。

5 让我们“并行”起来!

现在再来一点“Cool”的。

仔细考虑一下我们的“连网”判断逻辑,不难发现这些Ping操作是可以并行执行的,如果能使用多个线程同时执行Ping操作,无疑可以减少得到“是否可以访问互联网”这一最终结论的时间。

我们可以将IsOneLine方法转换为多线程版。

使用独立的线程执行每个网络接口的Ping操作,必须等待Ping网关的线程执行结束之后,依据其执行结果再决定是否新开一个线程执行Ping DNS的操作。
当有一个网卡可以Ping通DNS时,应该通知其它线程停止工作,因为“能否上网”的结论已经得出了。

这里面其实涉及到不少比较复杂的线程同步问题,需要用到多个线程同步对象,并且还涉及到如何中途取消一个线程执行的问题,我在《.NET 4.0面向对象编程漫谈 》的《应用篇》中,花了100多页的篇幅讲多线程,其中介绍了.NET 4.0基类库中几乎所有的线程同步对象的用法,并大力推荐推荐使用.NET 4.0的“线程统一取消模型”(参看《应用篇》的16.5 《线程统一取消模型》)来中途取消一个线程的执行。

读者您能否将IsOneLine方法转换为多线程版,是一块检测您是否真正掌握了多线程技术的试金石。

在这里,我想向读者介绍如何直接使用TPL(任务并行库)而不是线程达到目的。

需要仔细分析一下IsOnLine的处理逻辑,看看哪些部分是可以并行的,这些并行操作间有怎样的合作关系。

很明显,对于多个网络接口的连接检测工作完全是可以并行的,这是第一个任务并行点。


其次,一个网络接口一般只有一个网关,不需要并行,但一个网络接口可能会有两个以上的DNS服务地址,很明显,这是第二个任务并行点。


第三,在同时运行的多个并行任务之间,任何一个得到“互联网可达”最终结果的并行任务,需要通知其他任务提前中止执行。

经过以上考虑,使用并行循环(Parallel.ForEach)而不是Task对象是更合理的选择。Parallel.ForEach可以并行执行一个循环,并且通过ParallelLoopState对象可以提前中止循环,还可以“通知到”其他工作线程。

以下是使用任务并行库实现的IsOnLine版本:

static bool IsOnlineUseTPL()
{
if (NetworkInterface.GetIsNetworkAvailable() == false )
return false ;
// 选出所有处于激活状态的网卡
varquery = fromnic in NetworkInterface.GetAllNetworkInterfaces()
where nic.OperationalStatus == OperationalStatus.Up &&
nic.NetworkInterfaceType
!= NetworkInterfaceType.Loopback
selectnic;
int NicReady = 0 ; // 当有一个网卡的DNS是可达时,此值会大于0.
// 以下开始定义可并行的任务,针对每个网络接口的
Action < NetworkInterface,ParallelLoopState > PingNetworkInterface =
(nic,outState)
=>
{
Pingpinger
= new Ping(); // Ping网关
bool GatewayReady = false ;
foreach (GatewayIPAddressInformationGatewayAddr in
nic.GetIPProperties().GatewayAddresses)
{
if (outState.IsStopped) // 其他网卡的Ping循环已被中止
return ;
PingReplyreply
= pinger.Send(GatewayAddr.Address);
if (reply.Status == IPStatus.Success)
{
GatewayReady
= true ;
break ;
}
}
if (GatewayReady != true ) // 网关的都Ping不通,不必再PingDNS了
return ; // 结束本网卡的Ping工作
else // 网关是通的,就可并行Ping所有DNS地址
{
Parallel.ForEach (nic.GetIPProperties().DnsAddresses,
(dnsaddr,innerState)
=>
{
// 其他Ping循环(本网卡的或者是其它网卡的)已被中止
if (innerState.IsStopped || outState.IsStopped)
return ;
Pingpinger2 = new Ping();
PingReplyreply
= pinger2.Send(dnsaddr);
if (reply.Status == IPStatus.Success) // Ping成功
{
// 表明此网卡DNS可达
Interlocked.Increment( ref NicReady);
// 不需要再进行本并行循环的后继的循环工作
innerState.Stop();
outState.Stop();
// 上层的并行循环也可以中止了
return ;
}
}
);
}
};
// 并行任务定义结束
// 执行并行任务
Parallel.ForEach (query,PingNetworkInterface);
return NicReady > 0 ;
}


上述代码比较长,用到了一些比较复杂的编程特性,比如“LINQ查询”、“Lambda”表达式,Parallel并行循环等。请读者先仔细阅读 《.NET 4.0面向对象编程漫谈》的以下章节扫清知识障碍,再通过阅读代码中的注释,并在Visual Studio中进行调试才能看懂这些代码:

  • 8.4节《 匿名方法与Lambda表达式》
  • 11.5节《掌握LINQ查询表达式的编写技巧》
  • 17.2.3节《实现原子操作--Interlocked》
  • 19.3.3节《使用Parallel类编写并行代码》
  • 19.3.9 节《并行任务的取消》

如果希望将并行版本的IsOnlineTPL用于有可视化界面的程序,推荐创建一个新的背景线程执行此方法,然后使用第18章《跨线程访问可视化控件》所介绍的方法将结果“推送”到UI线程中,更新可视化界面(比如就象Windows一样切换显示图片)。

另外,.NET基类库中有一个NetworkChange密封类,当用户计算机的IP地址改变时(包括用户手动更改的,或者是用户关闭了无线功能等间接 原因造成)它可以引发一个NetworkAddressChanged事件,可以在此事件中调用IsOnlineTPL方法重新检测是否网络可达。

6 小结

由于我的网络环境比较单一,所以没法测试IsOnlineTPL方法是否可以在各种各样的网络环境下正常工作,另外,此方法仅仅检测到DNS是否“通” 为止,并没有去Ping一个真正的“互联网主机”,而且有些网络主机可能不理会“Ping”操作发出的数据包,因此,IsOnlineTPL返回 false并不一定意味着网络不可达。读者大可以在我的代码的基础上进行完善,或者是设计一个新的更好的实现方案。
本文仅起一个抛砖引玉的工作,如有错误,敬请朋友们回贴指出,不胜感激!


=======================================

点击下载本文所关联的示例源码

  点击阅读下一篇文章:

与Socket的第一次“约会”

相关文章:

  • 物联网系统设计初稿
  • Python xlsx 读取
  • S3C2440-启动分析
  • 2.3 js基础--DOM
  • [译]新的web应用:播下web3.0的种子
  • NSSearchPathForDirectoriesInDomains
  • ArcGIS 切片缓存紧凑文件格式分析与使用
  • CS224d Problem set 2作业
  • 一个日志输出系统的设计
  • linux内核内存管理中的pagevec结构体
  • poj_2352 线段树
  • Mac周边环境 goBASIC语言HelloWorld
  • linux内存管理系统后期的内核对zonelist的简化
  • bzoj3809: Gty的二逼妹子序列
  • linux内核page结构体的PG_referenced和PG_active标志
  • 77. Combinations
  • Cumulo 的 ClojureScript 模块已经成型
  • C语言笔记(第一章:C语言编程)
  • DOM的那些事
  • Js基础——数据类型之Null和Undefined
  • Otto开发初探——微服务依赖管理新利器
  • Terraform入门 - 1. 安装Terraform
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 分布式事物理论与实践
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 马上搞懂 GeoJSON
  • 前端攻城师
  • 前端设计模式
  • 让你的分享飞起来——极光推出社会化分享组件
  • 如何合理的规划jvm性能调优
  • 树莓派 - 使用须知
  • 微信开放平台全网发布【失败】的几点排查方法
  • 以太坊客户端Geth命令参数详解
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • (1)(1.13) SiK无线电高级配置(六)
  • (3)STL算法之搜索
  • (4) PIVOT 和 UPIVOT 的使用
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (二)Linux——Linux常用指令
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (四)Android布局类型(线性布局LinearLayout)
  • (一)Java算法:二分查找
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • .net core 控制台应用程序读取配置文件app.config
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .Net8 Blazor 尝鲜
  • ?php echo ?,?php echo Hello world!;?
  • @我的前任是个极品 微博分析
  • @在php中起什么作用?
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce
  • [Angular] 笔记 16:模板驱动表单 - 选择框与选项
  • [C++]C++基础知识概述
  • [cocos2d-x]关于CC_CALLBACK