虚位以待(AD)
虚位以待(AD)
首页 > 网络编程 > ASP.NET > 在.NET中扫描局域网服务的实现方法

在.NET中扫描局域网服务的实现方法
类别:ASP.NET   作者:码皇   来源:互联网   点击:

下面小编就为大家分享一篇在 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 扫描 局域网