扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
小编给大家分享一下.NET中如何实现扫描局域网服务,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
创新互联长期为上1000+客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为乌审企业提供专业的网站设计、成都网站建设,乌审网站改版等技术服务。拥有十多年丰富建站经验和众多成功案例,为您定制开发。在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 TCP 协议的请求的程序或者服务(如 WCF 服务)。
要实现这样的功能,核心的一点就是在得到当前机器同网段的所有机器的 IP 后,对每一 IP 发生 TCP 连接请求,如果请求超时或者出现其它异常,则认为没有服务,反之,如果能够正常连接,则认为服务正常。
经过基本功能的实现以及后续的重构之后,就有了本文以下的代码:一个接口和具体实现的类。需要说明的是:在下面的代码中,先提到接口,再提到具体类;而在开发过程中,则是首先创建了类,然后才提取了接口。之所以要提取接口,原因有二:一是可以支持 IoC控制反转;二是将来如果其它的同类需求,可以其于此接口实现新功能。
一、接口定义
先看来一下接口:
////// 扫描服务 /// public interface IServerScanner { ////// 扫描完成 /// event EventHandler> OnScanComplete; ///
/// 报告扫描进度 /// event EventHandlerOnScanProgressChanged; /// /// 扫描端口 /// int ScanPort { get; set; } ////// 单次连接超时时长 /// TimeSpan Timeout { get; set; } ////// 返回指定的IP与端口是否能够连接上 /// /// /// ///bool IsConnected(IPAddress ipAddress, int port); /// /// 返回指定的IP与端口是否能够连接上 /// /// /// ///bool IsConnected(string ip, int port); /// /// 开始扫描 /// void StartScan(); }
其中 Timeout 属性是控制每次连接请求超时的时长。
二、具体实现
再来看一下具体实现类:
////// 扫描结果 /// public class ConnectionResult { ////// IPAddress 地址 /// public IPAddress Address { get; set; } ////// 是否可连接上 /// public bool CanConnected { get; set; } } ////// 扫描完成事件参数 /// public class ScanCompleteEventArgs { ////// 结果集合 /// public ListReslut { get; set; } } /// /// 扫描进度事件参数 /// public class ScanProgressEventArgs { ////// 进度百分比 /// public int Percent { get; set; } } ////// 扫描局域网中的服务 /// public class ServerScanner : IServerScanner { ////// 同一网段内 IP 地址的数量 /// private const int SegmentIpMaxCount = 255; private DateTimeOffset _endTime; private object _locker = new object(); private SynchronizationContext _originalContext = SynchronizationContext.Current; private List_resultList = new List (); private DateTimeOffset _startTime; /// /// 记录调用/完成委托的数量 /// private int _totalCount = 0; public ServerScanner() { Timeout = TimeSpan.FromSeconds(2); } ////// 当扫描完成时,触发此事件 /// public event EventHandler> OnScanComplete; ///
/// 当扫描进度发生更改时,触发此事件 /// public event EventHandlerOnScanProgressChanged; /// /// 扫描端口 /// public int ScanPort { get; set; } ////// 单次请求的超时时长,默认为2秒 /// public TimeSpan Timeout { get; set; } ////// 使用 TcpClient 测试是否可以连上指定的 IP 与 Port /// /// /// ///public bool IsConnected(IPAddress ipAddress, int port) { var result = TestConnection(ipAddress, port); return result.CanConnected; } /// /// 使用 TcpClient 测试是否可以连上指定的 IP 与 Port /// /// /// ///public bool IsConnected(string ip, int port) { IPAddress ipAddress; if (IPAddress.TryParse(ip, out ipAddress)) { return IsConnected(ipAddress, port); } else { throw new ArgumentException("IP 地址格式不正确"); } } /// /// 开始扫描当前网段 /// public void StartScan() { if (ScanPort == 0) { throw new InvalidOperationException("必须指定扫描的端口 ScanPort"); } // 清除可能存在的数据 _resultList.Clear(); _totalCount = 0; _startTime = DateTimeOffset.Now; // 得到本网段的 IP var ipList = GetAllRemoteIPList(); // 生成委托列表 List> funcs = new List >(); for (int i = 0; i < SegmentIpMaxCount; i++) { var tmpF = new Func (TestConnection); funcs.Add(tmpF); } // 异步调用每个委托 for (int i = 0; i < SegmentIpMaxCount; i++) { funcs[i].BeginInvoke(ipList[i], ScanPort, OnComplete, funcs[i]); _totalCount += 1; } } /// /// 得到本网段的所有 IP /// ///private List GetAllRemoteIPList() { var localName = Dns.GetHostName(); var localIPEntry = Dns.GetHostEntry(localName); List ipList = new List (); 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 ; 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}"); } } } /// /// 测试是否可以连接到 /// /// /// ///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, Liste) { ... } private void ServerScanner_OnScanProgressChanged(object sender, ScanProgressEventArgs e) { ... }
以上是“.NET中如何实现扫描局域网服务”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注创新互联行业资讯频道!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流