zoukankan      html  css  js  c++  java
  • C#判断一个端口是不是被占用以及返回一个空闲端口

    一.引言

    在最近的工作当中,用到了 Socket 通信,然后要给 Socket 服务器端的监听获取一个空闲的本地监听端口。

    对于这个获取方法要满足如下几点的要求:

    1.  这个端口不能是别的程序所使用的端口;
    2.  这个获取要支持异步,即多个线程同时获取不会出现返回多个相同的空闲端口(即线程安全);
    3.  这端口要有效的遍历一个区域内的端口,直到返回一个可用的空闲端口;

     二.实现方法

    网上的实现方法主要有两种

    1. 使用 .NET 提供的 IPGlobaProperties.GetIPGlobaProperties() 来获得一个 IPGlobaProperties 对象,然后通过它的成员函数  GetActiveTcpListeners()、GetActiveUdpListeners() 以及 GetActiveTcpConnections() 来获得被连接或者被监听所使用了的端口,进而刷选出空闲的端口:

    //获取本地计算机的网络连接和通信统计数据的信息
    IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
    
    //返回本地计算机上的所有Tcp监听程序
    IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
    
    //返回本地计算机上的所有UDP监听程序
    IPEndPoint[] ipsUDP = ipProperties.GetActiveUdpListeners();
    
    //返回本地计算机上的Internet协议版本4(IPV4 传输控制协议(TCP)连接的信息
    TcpConnectionInformation[] tcpConnInfoArray = ipProperties.GetActiveTcpConnections();

    2. 使用 Process 创建一个命令行进程,执行命令 " netstat -an " 来获得所有的已经被使用的端口,我们仅仅通过 cmd 窗体输入这个命令的输出如下:

    我们通过匹配 " :端口号 " 是不是在上面返回的数据中就可以很容易的知道端口是不是被占用。


    经过测试之后发现,使用第一种方法有时候并不能检索到部分被使用了的端口,所以最后还是使用了第一种和第二种混合的检测方案

    三.程序代码

    通过第一种和第二种方法各查询一次并缓存,在本次查询中使用这个缓存(为了平衡效率与 " 在查找的时候被端口被占用 " 的问题)。于此同时,我们通过 lock 来避免异步问题,并且对于前后两次获取,如果前一个端口被获取到,那么我们之后的端口就从前一个的后面那个开始做查询。

    下面是程序的核心代码:

    public static class IPAndPortHelper
    {
        #region 成员字段
    
        /// <summary>
        /// 同步锁
        /// 用来在获得端口的时候同步两个线程
        /// </summary>
        private static object inner_asyncObject = new object();
    
        /// <summary>
        /// 开始的端口号
        /// </summary>
        private static int inner_startPort = 50001;
    
        #endregion
    
        #region 获得本机所使用的端口
    
        /// <summary>
        /// 使用 IPGlobalProperties 对象获得本机使用的端口
        /// </summary>
        /// <returns>本机使用的端口列表</returns>
        private static List<int> GetPortIsInOccupiedState()
        {
            List<int> retList = new List<int>();
            //遍历所有使用的端口,是不是与当前的端口有匹配
            try
            {
                //获取本地计算机的网络连接和通信统计数据的信息
                IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
                //返回本地计算机上的所有Tcp监听程序
                IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
                //返回本地计算机上的所有UDP监听程序
                IPEndPoint[] ipsUDP = ipProperties.GetActiveUdpListeners();
                //返回本地计算机上的Internet协议版本4(IPV4 传输控制协议(TCP)连接的信息
                TcpConnectionInformation[] tcpConnInfoArray = ipProperties.GetActiveTcpConnections();
    
                //将使用的端口加入
                retList.AddRange(ipEndPoints.Select(m => m.Port));
                retList.AddRange(ipsUDP.Select(m => m.Port));
                retList.AddRange(tcpConnInfoArray.Select(m => m.LocalEndPoint.Port));
                retList.Distinct();//去重
            }
            catch(Exception ex)//直接抛出异常
            {
                throw ex;
            }
    
            return retList;
        }
    
        /// <summary>
        /// 使用 NetStat 命令获得端口的字符串
        /// </summary>
        /// <returns>端口的字符串</returns>
        private static string GetPortIsInOccupiedStateByNetStat()
        {
            string output = string.Empty;
            try
            {
                using (Process process = new Process())
                {
                    process.StartInfo = new ProcessStartInfo("netstat", "-an");
                    process.StartInfo.CreateNoWindow = true;
                    process.StartInfo.UseShellExecute = false;
                    process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                    process.StartInfo.RedirectStandardOutput = true;
                    process.Start();
                    output = process.StandardOutput.ReadToEnd().ToLower();
                }
            }
            catch(Exception ex)
            {
                throw ex;
            }
    
            return output;
        }
    
        #endregion
    
        #region 获得一个当前没有被使用过的端口号
    
        /// <summary>
        /// 获得一个当前没有被使用过的端口号
        /// </summary>
        /// <returns>当前没有被使用过的端口号</returns>
        public static int GetUnusedPort()
        {
            /*
             * 在端口获取的时候防止两个进程同时获得一个一样的端口号
             * 在一个线程获得一个端口号的时候,下一个线程获取会从上一个线程获取的端口号+1开始查询
             */
            lock (inner_asyncObject)//线程安全
            {
                List<int> portList = GetPortIsInOccupiedState();
                string portString = GetPortIsInOccupiedStateByNetStat();
    
                for (int i = inner_startPort; i < 60000; i++)
                {
                    if (portString.IndexOf(":" + inner_startPort) < 0 &&
                        !portList.Contains(inner_startPort))
                    {
                        //记录一下 下次的端口查询从 inner_startPort+1 开始
                        inner_startPort = i + 1;
                        return i;
                    }
                }
    
                //如果获取不到
                return -1;
            }
        }
    
        #endregion
    } 

    测试代码:

    Console.WriteLine(IPAndPortHelper.GetUnusedPort());
    Console.WriteLine(IPAndPortHelper.GetUnusedPort());

    测试结果图:

    四.工程代码下载

    下载地址 

  • 相关阅读:
    用textfield制作richTextEdit
    鼠标手势替换闪烁不停地问题
    怎样在FlexBuilder中使用FlashCS中的组件[翻译]
    一个可以减少代码输入量的用法
    使用Flex library project
    初级程序员进步的几个标志
    给flashBuilder整几个快捷键
    在纯AS工程中嵌入个别字体方法
    软件设计中应重视的非技术因素
    ASP.NET MVC 2.0 Html.RenderPartial & Html.RenderAction
  • 原文地址:https://www.cnblogs.com/Jeffrey-Chou/p/12375743.html
Copyright © 2011-2022 走看看