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

测试结果图:

四.工程代码下载

下载地址

原文地址:https://www.cnblogs.com/Jeffrey-Chou/p/12375743.html

时间: 2024-08-29 03:35:51

C#判断一个端口是不是被占用以及返回一个空闲端口的相关文章

函数可以返回一个局部对象,而不能返回一个局部对象的引用(指针):

函数可以返回一个局部对象,而不能返回一个局部对象的引用(指针):当函数返回一个局部对象时,虽然这个对象已经释放,但是返回时会产生一个临时的对象.而当返回一个局部对象的引用时,这个对象已经不存在了.这就要求在函数参数中,包含一个引用或指针.int &func(int a,int b,int &retsult){ retsult = a + b; return &retsult}但是如下代码是错误的(返回局部对象的引用)int &func(int a,int b){ int &

设计一个程序,让它能够返回一个整数数组最大子数的和,并能完成几项测试

实验人员:常啸帆,毕文强 实验要求:必须能处理1000个元素: 每个元素是int32类型的,出现子数组之和大于显示最大范围会出现什么情况: 输入一个整形数组,有正也有负: 数组中连续的一个或多个整数组成一个数组,每个子数组都必须有一个和: 求所有子数组和的最大值,要求时间复杂度为o(n).实验过程:在本次实验中,我们两人通过所学的c++知识来编写程序,在过程中我们发现了一下问题并逐步解决 在编写的过程中,首先会发现数组中少了一个元素,那是因为在循环时length写成了length-1,     

linux解决端口号被占用

Linux下我们经常需要解决端口占用的问题,解决方法如下: linux: 1 某个端口号被占用,查看pid##根据端口查看进程信息 lsof -i:14700 ##根据进程号查看进程对应的可执行程序 ps -f -p 进程号 还可用netstatnetstat 的常用参数: - t.- u.- w和- x分别表示TCP.UDP.RAW和UNIX套接字连接.-a标记,还会显示出等待连接(也就是说处于监听模式)的套接字.-l 显示正在被监听(listen)的端口, -n表示直接显示端口数字而不是通过

获取系统空闲端口

端口取值范围 以下搜自互联网 一般用到的是1到65535,其中0不使用,1-1023为系统端口,也叫BSD保留端口;1024-65535为用户端口,又分为: BSD临时端口(1024-5000)和BSD服务器(非特权)端口(5001-65535). 0-1023: BSD保留端口,也叫系统端口,这些端口只有系统特许的进程才能使用; 1024-5000: BSD临时端口,一般的应用程序使用1024到4999来进行通讯; 5001-65535: BSD服务器(非特权)端口,用来给用户自定义端口. 常

设计一个方法,并输入一个数可以返回一个结果

/*做一个函数,我传一个数字给你,你返回一个结果给我. * 要求:如果数字小于100,则返回实际输入的数字: * 如果大于100小于200,则返回'100+': * 如果大于200小于300,则返回'200+':以此类推. */package homework; import java.util.Scanner; public class shuzi { public void shuzu() { Scanner input = new Scanner(System.in); System.ou

c++函数返回一个数组

---恢复内容开始--- 调用某个函数时经常需要函数返回一个值,我们都知道c++ 的函数返回的是一个copy,所以当只返回一个值时不会出现什么问题,直接return一个copy就行了,但是如果返回一个数组,事情就变得有趣了,我最近就遇到了这个问题. 先附上代码吧: #include<iostream> using namespace std; //函数声明 int * fun1(); int * fun2(); void dispArr(int *arr ,int n); const int

Entity Framework 6 Recipes 2nd Edition(11-1)译 -&gt; 从“模型定义”函数返回一个标量值

第11章函数 函数提供了一个有力代码复用机制, 并且让你的代码保持简洁和易懂. 它们同样也是EF运行时能利用的数据库层代码.函数有几类: Rowset Functions, 聚合函数, Ranking Functions, 和标量值函数. 函数要么确定,要么不确定.当用一些指定的值调用函数,而函数返回的结果总是一样时,它就是确定的函数.当甚至用同样的一些值调用时,而函数每次返回的结果也可能不一样,它就是不确定的函数. 在前七小节,我们探讨“模型定义”的函数,这些函数允许我们在概念层上创建.这些函

如何查看appache的端口是否被占用

win + R 快捷键输入 cmd 打开命令行. 输入 netstat -ano 查看端口使用情况 Ctrl + Shift + Esc 打开 windows 任务管理器,依次单击 [查看][ 选择列]. 在弹出的 [选择进程页列] 对话框中, 把第一项 [PID(进程标识符)] 选中. 关闭 [选择进程页列] 对话框, 放大 Windows 任务管理器中的 [进程] 选项卡 , 找到 PID 所对应的程序. 在我的电脑中是因为我装了一个监控管理程序,占用了 80 端口.导致我的 Apache 

java web中 8080端口号被占用的问题处理,终于明白了 Address already in use: JVM_Bind(端口冲突)

1.错误描述 2011-7-20 11:05:18 org.apache.catalina.core.StandardServer await严重: StandardServer.await: create[8005]: Java.NET.BindException: Address already in use: JVM_Bind at java.net.PlainSocketImpl.socketBind(Native Method) at java.Net.PlainSocketImpl.