最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
当前位置: 首页 - 科技 - 知识百科 - 正文

在.NET中扫描局域网服务的实现方法

来源:懂视网 责编:小采 时间:2020-11-27 22:35:10
文档

在.NET中扫描局域网服务的实现方法

在.NET中扫描局域网服务的实现方法:在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 TCP 协议的请求
推荐度:
导读在.NET中扫描局域网服务的实现方法:在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 TCP 协议的请求

在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 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中扫描局域网服务的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

文档

在.NET中扫描局域网服务的实现方法

在.NET中扫描局域网服务的实现方法:在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 TCP 协议的请求
推荐度:
标签: 中的 方法 扫描
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top