[聚合文章] 在 .NET 中,扫描局域网服务的实现

.Net 2018-01-18 18 阅读
在 .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;                    }
                

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。