在.NET中查找下一个TCP端口

83

我想为WCF服务调用创建一个新的net.tcp://localhost:x/Service端点,并动态分配一个新的开放TCP端口。

我知道当我连接到给定的服务器时,TcpClient会分配一个新的客户端端口。

有没有一种简单的方法在.NET中找到下一个空闲的TCP端口?

我需要实际的数字,以便我可以构建上面的字符串。0不起作用,因为我需要将该字符串传递给另一个进程,以便我可以在该新通道上回调。


1
https://dev59.com/o1DTa4cB1Zd3GeqPFgSl - keza
8个回答

152

这就是我在寻找的内容:

static int FreeTcpPort()
{
  TcpListener l = new TcpListener(IPAddress.Loopback, 0);
  l.Start();
  int port = ((IPEndPoint)l.LocalEndpoint).Port;
  l.Stop();
  return port;
}

14
如果另一个进程在您重新打开端口之前打开了该端口,会发生什么情况...? - Mike Christiansen
9
当然,那样做会导致错误,但在我的情境下这不是问题。 - TheSeeker
15
我成功地使用了这种技术来获得一个免费的端口。我也担心会发生竞态条件,其他一些进程可能会突然出现并占用最近检测到的空闲端口。因此,在使用FreeTcpPort()获取空闲端口和在该端口上启动HttpListener之间,我加入了一个Sleep(100)的强制等待,并编写了一个测试。我随后运行了8个相同的进程,对其进行循环测试。但我从未遇到过竞争条件。根据我的经验(Win 7),操作系统显然会在整个短暂端口范围内循环(几千个)之后再次回到起点。因此,上述代码应该是没有问题的。 - Doug Schmidt
3
如果我正确理解了这个工作原理,你不只是可以这样做吗:TcpListener myActualServer = new TcpListener(ipAddress, 0); myActualServer.Start(); return ((IPEndPoint)myActualServer.LocalEndpoint).Port; 或者重要的是在真正启动监听器之前让OP获得端口号? - JakeStrang
需要注意的一点是,当我尝试使用这段代码时,它会优先考虑临时端口。对于大多数用例来说,这并不是问题,但对于我们来说却是。 - Connor McMahon
显示剩余2条评论

34

使用端口号0,TCP协议栈会分配下一个可用的端口。


6
这行不通,因为我需要实际的数字而不仅仅是0。我需要这个数字来构建一个字符串。 - TheSeeker
这个对我很有帮助。如果你通知另一方你正在监听的端口,它实际上是有效的。 - user35443
1
ServiceHost host = new ServiceHost(typeof(MyService), new Uri("net.tcp://localhost:0/Service"));这不会起作用吗? - Joel McBeth
是的,它可以工作。你只需要将 endpoint.ListenUriMode 设置为 Unique - Yegor
这对我来说是一个很好的解决方案。使用 System.Net.Sockets.TcpClient。 - Billy Bob

20

这个解决方案与TheSeeker的被接受答案相媲美,但我认为它更易读:

using System;
using System.Net;
using System.Net.Sockets;

    private static readonly IPEndPoint DefaultLoopbackEndpoint = new IPEndPoint(IPAddress.Loopback, port: 0);

    public static int GetAvailablePort()
    {
        using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            socket.Bind(DefaultLoopbackEndpoint);
            return ((IPEndPoint)socket.LocalEndPoint).Port;
        }
    }

1
对于 UDP,我使用了以下代码:... SocketType.Dgram, ProtocolType.Udp... - BatteryAcid

13

我从 Selenium.WebDriver DLL 中找到了下面的代码:

命名空间:OpenQA.Selenium.Internal

:PortUtility

public static int FindFreePort()
{
    int port = 0;
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    try
    {
        IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 0);
        socket.Bind(localEP);
        localEP = (IPEndPoint)socket.LocalEndPoint;
        port = localEP.Port;
    }
    finally
    {
        socket.Close();
    }
    return port;
}

11

如果您只想提供一个起始端口,并让它返回下一个可用的TCP端口,请使用以下代码:

public static int GetAvailablePort(int startingPort)
{
    var portArray = new List<int>();

    var properties = IPGlobalProperties.GetIPGlobalProperties();

    // Ignore active connections
    var connections = properties.GetActiveTcpConnections();
    portArray.AddRange(from n in connections
                        where n.LocalEndPoint.Port >= startingPort
                        select n.LocalEndPoint.Port);

    // Ignore active tcp listners
    var endPoints = properties.GetActiveTcpListeners();
    portArray.AddRange(from n in endPoints
                        where n.Port >= startingPort
                        select n.Port);

    // Ignore active UDP listeners
    endPoints = properties.GetActiveUdpListeners();
    portArray.AddRange(from n in endPoints
                        where n.Port >= startingPort
                        select n.Port);

    portArray.Sort();

    for (var i = startingPort; i < UInt16.MaxValue; i++)
        if (!portArray.Contains(i))
            return i;

    return 0;
}

完美运行! - Bestter
请注意,由于某些限制,此代码无法在Azure中运行。您将收到“访问被拒绝”的错误提示。同样的问题也会出现在使用netstat时。 - Samuel Kupferschmid
1
谢谢,这个可行。小建议:你可以删除UDP检查,因为TCP和UDP端口存在不同的命名空间 - 3dGrabber

9

首先打开端口,然后将正确的端口号提供给其他进程。

否则,仍有可能出现其他进程先打开端口而您使用不同的端口号的情况。


2
你有获取TCP端口的代码示例吗?最简单的方式是什么? - TheSeeker
这是正确的做法。必须小心,如果多个进程打开相同的端口,可能会出现竞争条件。 - Mike Christiansen

3

如果您想在给定范围内找到下一个可用的TCP端口,以下是更简洁的实现方式:

private int GetNextUnusedPort(int min, int max)
{
    if (max < min)
        throw new ArgumentException("Max cannot be less than min.");

    var ipProperties = IPGlobalProperties.GetIPGlobalProperties();

    var usedPorts =
        ipProperties.GetActiveTcpConnections()
            .Where(connection => connection.State != TcpState.Closed)
            .Select(connection => connection.LocalEndPoint)
            .Concat(ipProperties.GetActiveTcpListeners())
            .Concat(ipProperties.GetActiveUdpListeners())
            .Select(endpoint => endpoint.Port)
            .ToArray();

    var firstUnused =
        Enumerable.Range(min, max - min)
            .Where(port => !usedPorts.Contains(port))
            .Select(port => new int?(port))
            .FirstOrDefault();

    if (!firstUnused.HasValue)
        throw new Exception($"All local TCP ports between {min} and {max} are currently in use.");

    return firstUnused.Value;
}

1
如果您想获得特定范围内的免费端口,以便将其用作本地端口/终点,请执行以下操作:
private int GetFreePortInRange(int PortStartIndex, int PortEndIndex)
{
    DevUtils.LogDebugMessage(string.Format("GetFreePortInRange, PortStartIndex: {0} PortEndIndex: {1}", PortStartIndex, PortEndIndex));
    try
    {
        IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();

        IPEndPoint[] tcpEndPoints = ipGlobalProperties.GetActiveTcpListeners();
        List<int> usedServerTCpPorts = tcpEndPoints.Select(p => p.Port).ToList<int>();

        IPEndPoint[] udpEndPoints = ipGlobalProperties.GetActiveUdpListeners();
        List<int> usedServerUdpPorts = udpEndPoints.Select(p => p.Port).ToList<int>();

        TcpConnectionInformation[] tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections();
        List<int> usedPorts = tcpConnInfoArray.Where(p=> p.State != TcpState.Closed).Select(p => p.LocalEndPoint.Port).ToList<int>();

        usedPorts.AddRange(usedServerTCpPorts.ToArray());
        usedPorts.AddRange(usedServerUdpPorts.ToArray());

        int unusedPort = 0;

        for (int port = PortStartIndex; port < PortEndIndex; port++)
        {
            if (!usedPorts.Contains(port))
            {
                unusedPort = port;
                break;
            }
        }
        DevUtils.LogDebugMessage(string.Format("Local unused Port:{0}", unusedPort.ToString()));

        if (unusedPort == 0)
        {
            DevUtils.LogErrorMessage("Out of ports");
            throw new ApplicationException("GetFreePortInRange, Out of ports");
        }

        return unusedPort;
    }
    catch (Exception ex)
    {
        string errorMessage = ex.Message;
        DevUtils.LogErrorMessage(errorMessage);
        throw;
    }
}


private int GetLocalFreePort()
{
    int hemoStartLocalPort = int.Parse(DBConfig.GetField("Site.Config.hemoStartLocalPort"));
    int hemoEndLocalPort = int.Parse(DBConfig.GetField("Site.Config.hemoEndLocalPort"));
    int localPort = GetFreePortInRange(hemoStartLocalPort, hemoEndLocalPort);
    DevUtils.LogDebugMessage(string.Format("Local Free Port:{0}", localPort.ToString()));
    return localPort;
}


public void Connect(string host, int port)
{
    try
    {
        // Create socket
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

        var localPort = GetLocalFreePort();
        // Create an endpoint for the specified IP on any port
        IPEndPoint bindEndPoint = new IPEndPoint(IPAddress.Any, localPort);

        // Bind the socket to the endpoint
        socket.Bind(bindEndPoint);

        // Connect to host
        socket.Connect(IPAddress.Parse(host), port);

        socket.Dispose();
    }
    catch (SocketException ex)
    {
        // Get the error message
        string errorMessage = ex.Message;
        DevUtils.LogErrorMessage(errorMessage);
    }
}


public void Connect2(string host, int port)
{
    try
    {
        // Create socket

        var localPort = GetLocalFreePort();

        // Create an endpoint for the specified IP on any port
        IPEndPoint bindEndPoint = new IPEndPoint(IPAddress.Any, localPort);

        var client = new TcpClient(bindEndPoint);
        //client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); //will release port when done

        // Connect to the host
        client.Connect(IPAddress.Parse(host), port);

        client.Close();
    }
    catch (SocketException ex)
    {
        // Get the error message
        string errorMessage = ex.Message;
        DevUtils.LogErrorMessage(errorMessage);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接