在.NET中扫描局域网服务的实现方法
在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于TCP协议的请求的程序或者服务(如WCF服务)。
要实现这样的功能,核心的一点就是在得到当前机器同网段的所有机器的IP后,对每一IP发生TCP连接请求,如果请求超时或者出现其它异常,则认为没有服务,反之,如果能够正常连接,则认为服务正常。
经过基本功能的实现以及后续的重构之后,就有了本文以下的代码:一个接口和具体实现的类。需要说明的是:在下面的代码中,先提到接口,再提到具体类;而在开发过程中,则是首先创建了类,然后才提取了接口。之所以要提取接口,原因有二:一是可以支持IoC控制反转;二是将来如果其它的同类需求,可以其于此接口实现新功能。
一、接口定义
先看来一下接口:
//////扫描服务 /// publicinterfaceIServerScanner { //////扫描完成 /// eventEventHandler>OnScanComplete; ///
///报告扫描进度 /// eventEventHandlerOnScanProgressChanged; /// ///扫描端口 /// intScanPort{get;set;} //////单次连接超时时长 /// TimeSpanTimeout{get;set;} //////返回指定的IP与端口是否能够连接上 /// ////// /// boolIsConnected(IPAddressipAddress,intport); /// ///返回指定的IP与端口是否能够连接上 /// ////// /// boolIsConnected(stringip,intport); /// ///开始扫描 /// voidStartScan(); }
其中Timeout属性是控制每次连接请求超时的时长。
二、具体实现
再来看一下具体实现类:
//////扫描结果 /// publicclassConnectionResult { //////IPAddress地址 /// publicIPAddressAddress{get;set;} //////是否可连接上 /// publicboolCanConnected{get;set;} } //////扫描完成事件参数 /// publicclassScanCompleteEventArgs { //////结果集合 /// publicListReslut{get;set;} } /// ///扫描进度事件参数 /// publicclassScanProgressEventArgs { //////进度百分比 /// publicintPercent{get;set;} } //////扫描局域网中的服务 /// publicclassServerScanner:IServerScanner { //////同一网段内IP地址的数量 /// privateconstintSegmentIpMaxCount=255; privateDateTimeOffset_endTime; privateobject_locker=newobject(); privateSynchronizationContext_originalContext=SynchronizationContext.Current; privateList_resultList=newList (); privateDateTimeOffset_startTime; /// ///记录调用/完成委托的数量 /// privateint_totalCount=0; publicServerScanner() { Timeout=TimeSpan.FromSeconds(2); } //////当扫描完成时,触发此事件 /// publiceventEventHandler>OnScanComplete; ///
///当扫描进度发生更改时,触发此事件 /// publiceventEventHandlerOnScanProgressChanged; /// ///扫描端口 /// publicintScanPort{get;set;} //////单次请求的超时时长,默认为2秒 /// publicTimeSpanTimeout{get;set;} //////使用TcpClient测试是否可以连上指定的IP与Port /// ////// /// publicboolIsConnected(IPAddressipAddress,intport) { varresult=TestConnection(ipAddress,port); returnresult.CanConnected; } /// ///使用TcpClient测试是否可以连上指定的IP与Port /// ////// /// publicboolIsConnected(stringip,intport) { IPAddressipAddress; if(IPAddress.TryParse(ip,outipAddress)) { returnIsConnected(ipAddress,port); } else { thrownewArgumentException("IP地址格式不正确"); } } /// ///开始扫描当前网段 /// publicvoidStartScan() { if(ScanPort==0) { thrownewInvalidOperationException("必须指定扫描的端口ScanPort"); } //清除可能存在的数据 _resultList.Clear(); _totalCount=0; _startTime=DateTimeOffset.Now; //得到本网段的IP varipList=GetAllRemoteIPList(); //生成委托列表 List>funcs=newList >(); for(inti=0;i (TestConnection); funcs.Add(tmpF); } //异步调用每个委托 for(inti=0;i ///得到本网段的所有IP /// /// privateList GetAllRemoteIPList() { varlocalName=Dns.GetHostName(); varlocalIPEntry=Dns.GetHostEntry(localName); List ipList=newList (); IPAddresslocalInterIP=localIPEntry.AddressList.FirstOrDefault(m=>m.AddressFamily==AddressFamily.InterNetwork); if(localInterIP==null) { thrownewInvalidOperationException("当前计算机不存在内网IP"); } varlocalInterIPBytes=localInterIP.GetAddressBytes(); for(inti=1;i<=SegmentIpMaxCount;i++) { //对末位进行替换 localInterIPBytes[3]=(byte)i; ipList.Add(newIPAddress(localInterIPBytes)); } returnipList; } privatevoidOnComplete(IAsyncResultar) { varstate=ar.AsyncStateasFunc ; varresult=state.EndInvoke(ar); lock(_locker) { //添加到结果中 _resultList.Add(result); //报告进度 _totalCount-=1; varpercent=(SegmentIpMaxCount-_totalCount)*100/SegmentIpMaxCount; if(SynchronizationContext.Current==_originalContext) { OnScanProgressChanged?.Invoke(this,newScanProgressEventArgs{Percent=percent}); } else { _originalContext.Post(conState=> { OnScanProgressChanged?.Invoke(this,newScanProgressEventArgs{Percent=percent}); },null); } if(_totalCount==0) { //通过事件抛出结果 if(SynchronizationContext.Current==_originalContext) { OnScanComplete?.Invoke(this,_resultList); } else { _originalContext.Post(conState=> { OnScanComplete?.Invoke(this,_resultList); },null); } //计算耗时 Debug.WriteLine("Compete"); _endTime=DateTimeOffset.Now; Debug.WriteLine($"Duration:{_endTime-_startTime}"); } } } /// ///测试是否可以连接到 /// ////// /// privateConnectionResultTestConnection(IPAddressaddress,intport) { TcpClientc=newTcpClient(); ConnectionResultresult=newConnectionResult(); result.Address=address; using(TcpClienttcp=newTcpClient()) { IAsyncResultar=tcp.BeginConnect(address,port,null,null); WaitHandlewh=ar.AsyncWaitHandle; try { if(!ar.AsyncWaitHandle.WaitOne(Timeout,false)) { tcp.Close(); } else { tcp.EndConnect(ar); result.CanConnected=true; } } catch { } finally { wh.Close(); } } returnresult; } } ServerScanner
以上代码中注释基本上已经比较详细,这里再简单提几个点:
TestConnection函数实了现核心功能,即请求给定的IP和端口,并返回结果;其中通过调用IAsyncResult.AsyncWaitHandle属性的WaitOne方法来实现对超时的控制;
StartScan方法中,在得到IP列表后,通过生成委托列表并异步调用这些委托来实现整个方法是异步的,不会阻塞UI,而这些委托指向的方法就是TestConnection函数;
使用同步上下文SynchronizationContext,可以保证调用方在原来的线程(通常是UI线程)上处理进度更新事件或扫描完成事件;
对于每个委托异步完成后,会执行回调方法OnComplete,在它里面,对全局变量的操作需要加锁,以保证线程安全。
三、如何使用
最后来看一下如何使用,非常简单:
privatevoidView_Loaded() { //在界面Load事件中添加以下代码 ServerScanner.OnScanComplete+=ServerScanner_OnScanComplete; ServerScanner.OnScanProgressChanged+=ServerScanner_OnScanProgressChanged; //扫描的端口号 ServerScanner.ScanPort=7890; } privatevoidStartScan() { //开始扫描 ServerScanner.StartScan(); } privatevoidServerScanner_OnScanComplete(objectsender,Liste) { ... } privatevoidServerScanner_OnScanProgressChanged(objectsender,ScanProgressEventArgse) { ... }
如果你有更好的建议或意见,请留言互相交流。
以上这篇在.NET中扫描局域网服务的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。