我想为 WCF 服务调用创建一个新的 net.tcp://localhost:x/Service 端点,并动态分配一个新的开放 TCP 端口。
我知道当我打开与给定服务器的连接时,TcpClient 将分配一个新的客户端端口。
有没有一种简单的方法可以在 .NET 中找到下一个打开的 TCP 端口?
我需要实际的数字,以便我可以构建上面的字符串。 0 不起作用,因为我需要将该字符串传递给另一个进程,以便我可以在该新通道上回调。
这是我一直在寻找的:
static int FreeTcpPort()
{
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
int port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
使用端口号 0。TCP 堆栈将分配下一个空闲端口。
endpoint.ListenUriMode
设置为 Unique
。
这是一个与 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;
}
}
... SocketType.Dgram, ProtocolType.Udp...
我从 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;
}
如果您只想提供一个起始端口,并让它返回下一个可用的 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;
}
首先打开端口,然后将正确的端口号提供给其他进程。
否则,仍然有可能其他进程先打开端口,而您仍然有另一个进程。
如果您想在给定范围内找到下一个可用的 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;
}
如果您想获得特定范围内的空闲端口以将其用作本地端口/端点:
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);
}
}
var port = FreeTcpPort()
和在空闲端口上启动 HttpListener 之间编写了一个强制Sleep(100)
的测试。然后,我运行了 8 个相同的进程来循环执行此操作。我永远无法达到比赛条件。我的轶事证据(Win 7)是操作系统显然会在短暂端口(几千个)范围内循环,然后再次出现。所以上面的片段应该没问题。TcpListener myActualServer = new TcpListener(ipAddress, 0); myActualServer.Start(); return ((IPEndPoint)myActualServer.LocalEndpoint).Port;
或者 OP 在实际启动他的监听器之前获取端口是否重要?