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());

测试结果图:

四.工程代码下载

下载地址 

posted @ 2020-03-19 20:42  霁雪湖上三映月  阅读(2113)  评论(1编辑  收藏  举报