在.NET中扫描局域网服务的实现方法
更新时间:2018年01月19日 08:45:16 作者:WPInfo
下面小编就为大家分享一篇在.NET中扫描局域网服务的实现方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 TCP 协议的请求的程序或者服务(如 WCF 服务)。
要实现这样的功能,核心的一点就是在得到当前机器同网段的所有机器的 IP 后,对每一 IP 发生 TCP 连接请求,如果请求超时或者出现其它异常,则认为没有服务,反之,如果能够正常连接,则认为服务正常。
经过基本功能的实现以及后续的重构之后,就有了本文以下的代码:一个接口和具体实现的类。需要说明的是:在下面的代码中,先提到接口,再提到具体类;而在开发过程中,则是首先创建了类,然后才提取了接口。之所以要提取接口,原因有二:一是可以支持 IoC控制反转;二是将来如果其它的同类需求,可以其于此接口实现新功能。
一、接口定义
先看来一下接口:
/// <summary>
/// 扫描服务
/// </summary>
public interface IServerScanner
{
/// <summary>
/// 扫描完成
/// </summary>
event EventHandler<List<ConnectionResult>> OnScanComplete;
/// <summary>
/// 报告扫描进度
/// </summary>
event EventHandler<ScanProgressEventArgs> OnScanProgressChanged;
/// <summary>
/// 扫描端口
/// </summary>
int ScanPort { get; set; }
/// <summary>
/// 单次连接超时时长
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// 返回指定的IP与端口是否能够连接上
/// </summary>
/// <param name=”ipAddress”></param>
/// <param name=”port”></param>
/// <returns></returns>
bool IsConnected(IPAddress ipAddress, int port);
/// <summary>
/// 返回指定的IP与端口是否能够连接上
/// </summary>
/// <param name=”ip”></param>
/// <param name=”port”></param>
/// <returns></returns>
bool IsConnected(string ip, int port);
/// <summary>
/// 开始扫描
/// </summary>
void StartScan();
}
其中 Timeout 属性是控制每次连接请求超时的时长。
二、具体实现
再来看一下具体实现类:
/// <summary>
/// 扫描结果
/// </summary>
public class ConnectionResult
{
/// <summary>
/// IPAddress 地址
/// </summary>
public IPAddress Address { get; set; }
/// <summary>
/// 是否可连接上
/// </summary>
public bool CanConnected { get; set; }
}
/// <summary>
/// 扫描完成事件参数
/// </summary>
public class ScanCompleteEventArgs
{
/// <summary>
/// 结果集合
/// </summary>
public List<ConnectionResult> Reslut { get; set; }
}
/// <summary>
/// 扫描进度事件参数
/// </summary>
public class ScanProgressEventArgs
{
/// <summary>
/// 进度百分比
/// </summary>
public int Percent { get; set; }
}
/// <summary>
/// 扫描局域网中的服务
/// </summary>
public class ServerScanner : IServerScanner
{
/// <summary>
/// 同一网段内 IP 地址的数量
/// </summary>
private const int SegmentIpMaxCount = 255;
private DateTimeOffset _endTime;
private object _locker = new object();
private SynchronizationContext _originalContext = SynchronizationContext.Current;
private List<ConnectionResult> _resultList = new List<ConnectionResult>();
private DateTimeOffset _startTime;
/// <summary>
/// 记录调用/完成委托的数量
/// </summary>
private int _totalCount = 0;
public ServerScanner()
{
Timeout = TimeSpan.FromSeconds(2);
}
/// <summary>
/// 当扫描完成时,触发此事件
/// </summary>
public event EventHandler<List<ConnectionResult>> OnScanComplete;
/// <summary>
/// 当扫描进度发生更改时,触发此事件
/// </summary>
public event EventHandler<ScanProgressEventArgs> OnScanProgressChanged;
/// <summary>
/// 扫描端口
/// </summary>
public int ScanPort { get; set; }
/// <summary>
/// 单次请求的超时时长,默认为2秒
/// </summary>
public TimeSpan Timeout { get; set; }
/// <summary>
/// 使用 TcpClient 测试是否可以连上指定的 IP 与 Port
/// </summary>
/// <param name=”ipAddress”></param>
/// <param name=”port”></param>
/// <returns></returns>
public bool IsConnected(IPAddress ipAddress, int port)
{
var result = TestConnection(ipAddress, port);
return result.CanConnected;
}
/// <summary>
/// 使用 TcpClient 测试是否可以连上指定的 IP 与 Port
/// </summary>
/// <param name=”ip”></param>
/// <param name=”port”></param>
/// <returns></returns>
public bool IsConnected(string ip, int port)
{
IPAddress ipAddress;
if (IPAddress.TryParse(ip, out ipAddress))
{
return IsConnected(ipAddress, port);
}
else
{
throw new ArgumentException(“IP 地址格式不正确”);
}
}
/// <summary>
/// 开始扫描当前网段
/// </summary>
public void StartScan()
{
if (ScanPort == 0)
{
throw new InvalidOperationException(“必须指定扫描的端口 ScanPort”);
}
// 清除可能存在的数据
_resultList.Clear();
_totalCount = 0;
_startTime = DateTimeOffset.Now;
// 得到本网段的 IP
var ipList = GetAllRemoteIPList();
// 生成委托列表
List<Func<IPAddress, int, ConnectionResult>> funcs = new List<Func<IPAddress, int, ConnectionResult>>();
for (int i = 0; i < SegmentIpMaxCount; i++)
{
var tmpF = new Func<IPAddress, int, ConnectionResult>(TestConnection);
funcs.Add(tmpF);
}
// 异步调用每个委托
for (int i = 0; i < SegmentIpMaxCount; i++)
{
funcs[i].BeginInvoke(ipList[i], ScanPort, OnComplete, funcs[i]);
_totalCount += 1;
}
}
/// <summary>
/// 得到本网段的所有 IP
/// </summary>
/// <returns></returns>
private List<IPAddress> GetAllRemoteIPList()
{
var localName = Dns.GetHostName();
var localIPEntry = Dns.GetHostEntry(localName);
List<IPAddress> ipList = new List<IPAddress>();
IPAddress localInterIP = localIPEntry.AddressList.FirstOrDefault(m => m.AddressFamily == AddressFamily.InterNetwork);
if (localInterIP == null)
{
throw new InvalidOperationException(“当前计算机不存在内网 IP”);
}
var localInterIPBytes = localInterIP.GetAddressBytes();
for (int i = 1; i <= SegmentIpMaxCount; i++)
{
// 对末位进行替换
localInterIPBytes[3] = (byte)i;
ipList.Add(new IPAddress(localInterIPBytes));
}
return ipList;
}
private void OnComplete(IAsyncResult ar)
{
var state = ar.AsyncState as Func<IPAddress, int, ConnectionResult>;
var result = state.EndInvoke(ar);
lock (_locker)
{
// 添加到结果中
_resultList.Add(result);
// 报告进度
_totalCount -= 1;
var percent = (SegmentIpMaxCount – _totalCount) * 100 / SegmentIpMaxCount;
if (SynchronizationContext.Current == _originalContext)
{
OnScanProgressChanged?.Invoke(this, new ScanProgressEventArgs { Percent = percent });
}
else
{
_originalContext.Post(conState =>
{
OnScanProgressChanged?.Invoke(this, new ScanProgressEventArgs { 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}”);
}
}
}
/// <summary>
/// 测试是否可以连接到
/// </summary>
/// <param name=”address”></param>
/// <param name=”port”></param>
/// <returns></returns>
private ConnectionResult TestConnection(IPAddress address, int port)
{
TcpClient c = new TcpClient();
ConnectionResult result = new ConnectionResult();
result.Address = address;
using (TcpClient tcp = new TcpClient())
{
IAsyncResult ar = tcp.BeginConnect(address, port, null, null);
WaitHandle wh = ar.AsyncWaitHandle;
try
{
if (!ar.AsyncWaitHandle.WaitOne(Timeout, false))
{
tcp.Close();
}
else
{
tcp.EndConnect(ar);
result.CanConnected = true;
}
}
catch
{
}
finally
{
wh.Close();
}
}
return result;
}
}
ServerScanner
以上代码中注释基本上已经比较详细,这里再简单提几个点:
TestConnection 函数实了现核心功能,即请求给定的 IP 和端口,并返回结果;其中通过调用 IAsyncResult.AsyncWaitHandle 属性的 WaitOne 方法来实现对超时的控制;
StartScan 方法中,在得到 IP 列表后,通过生成委托列表并异步调用这些委托来实现整个方法是异步的,不会阻塞 UI,而这些委托指向的方法就是 TestConnection 函数;
使用同步上下文 SynchronizationContext,可以保证调用方在原来的线程(通常是 UI 线程)上处理进度更新事件或扫描完成事件;
对于每个委托异步完成后,会执行回调方法 OnComplete,在它里面,对全局变量的操作需要加锁,以保证线程安全。
三、如何使用
最后来看一下如何使用,非常简单:
private void View_Loaded()
{
// 在界面 Load 事件中添加以下代码
ServerScanner.OnScanComplete += ServerScanner_OnScanComplete;
ServerScanner.OnScanProgressChanged += ServerScanner_OnScanProgressChanged;
// 扫描的端口号
ServerScanner.ScanPort = 7890;
}
private void StartScan()
{
// 开始扫描
ServerScanner.StartScan();
}
private void ServerScanner_OnScanComplete(object sender, List<ConnectionResult> e)
{
…
}
private void ServerScanner_OnScanProgressChanged(object sender, ScanProgressEventArgs e)
{
…
}
如果你有更好的建议或意见,请留言互相交流。
以上这篇在.NET中扫描局域网服务的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持华域联盟。
您可能感兴趣的文章:asp.net实现访问局域网共享目录下文件的解决方法
.NET
扫描
局域网
相关文章
.NET Core2.1如何获取自定义配置文件信息详解这篇文章主要给大家介绍了关于.NET Core2.1如何获取自定义配置文件信息的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 2018-08-08
ASP.NET中Session和Cache的区别总结这篇文章主要介绍了ASP.NET中Session和Cache的区别总结,本文结合使用经验,总结出了5点Session缓存和Cache缓存的区别,需要的朋友可以参考下 2015-06-06
将DataTable中的一行复制到另一个DataTable的方法将DataTable中的一行复制到另一个DataTable的方法… 2007-09-09
asp.net中GridView控件遍历的小例子在asp.net中要遍历像数据之类的内容我们一般会用到for,foreach,while这种了,下面我来介绍利用for遍历GridView控件 2013-08-08
详解Asp.net web.config customErrors 如何设置这篇文章主要介绍了详解Asp.net web.config customErrors 如何设置,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 2018-02-02
Asp.net后台把脚本样式输出到head标签中节省代码冗余最近在学习开发服务器控件,其它就少不了为控件注册js和css之类的资源文件,或者直接注册纯脚本样式。其中就遇到如下问题
1、 注册的资源文件或纯脚本样式在生成的页面中都不在head标签中(当然这个不影响页面功能)
2、 一个页面使用多个一样的控件时,会出现重复输入(出现多余代码)
2013-02-02
MongoDB.Net工具库MongoRepository使用方法详解这篇文章主要为大家详细介绍了MongoDB.Net工具库MongoRepository的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 2018-01-01
Request.UrlReferrer使用详解Request.UrlReferrer可以获取客户端上次请求的url的有关信息,接下来为大家详细介绍下Request.UrlReferrer使用方法,感兴趣的朋友可以参考下哈,希望对你有所帮助 2013-04-04
一个Asp.Net的显示分页方法 附加实体转换和存储过程 带源码下载现在自己写的webform都不用服务器控件了,所以自己仿照aspnetpager写了一个精简实用的返回分页显示的html方法,其他话不说了,直接上代码 2012-10-10
在GridView中LinkButton的属性的应用(如何不用选中就删除这一行GridView中LinkButton的属性的应用,实现不用选中就删除这一行 2009-04-04
最新评论

评论(0)