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

測試結果圖:

四.工程代碼下載

下載地址 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM