如何在 C# 中的随机端口上创建 HttpListener 类?
Posted
技术标签:
【中文标题】如何在 C# 中的随机端口上创建 HttpListener 类?【英文标题】:How can I create an HttpListener class on a random port in C#? 【发布时间】:2010-09-18 09:27:09 【问题描述】:我想创建一个在内部为网页提供服务的应用程序,并且可以在同一台机器上的多个实例中运行。为此,我想创建一个 HttpListener
来侦听以下端口:
-
随机选择
目前未使用
基本上,我想要的是这样的:
mListener = new HttpListener();
mListener.Prefixes.Add("http://*:0/");
mListener.Start();
selectedPort = mListener.Port;
我怎样才能做到这一点?
【问题讨论】:
【参考方案1】:这样的事情怎么样:
static List<int> usedPorts = new List<int>();
static Random r = new Random();
public HttpListener CreateNewListener()
HttpListener mListener;
int newPort = -1;
while (true)
mListener = new HttpListener();
newPort = r.Next(49152, 65535); // IANA suggests the range 49152 to 65535 for dynamic or private ports.
if (usedPorts.Contains(newPort))
continue;
mListener.Prefixes.Add(string.Format("http://*:0/", newPort));
try
mListener.Start();
catch
continue;
usedPorts.Add(newPort);
break;
return mListener;
我不确定您如何找到该机器上正在使用的所有端口,但是如果您尝试侦听已被使用的端口,您应该会遇到异常,在这种情况下,该方法将只需选择另一个端口。
【讨论】:
您可能需要考虑改用MinPort
/MaxPort
常量MSDN link
@JosephLennox,MinPort
常数为零。答案中给出的最小值更合适。
@Snooganz,catch 块可能应该处理mListener
。此外,usedPorts
应该是 Set<int>
用于线性成员资格测试。就我个人而言,我会将usedPorts
限定在该方法中(或完全摆脱它),就好像您在一个运行时间很长的系统中使用它一样,您最终可能会耗尽端口,从而使while
循环永远运行,即使端口随着时间的推移变得可用。【参考方案2】:
如果绑定到端口 0,TcpListener 会随机找到一个未使用的端口来监听。
public static int GetRandomUnusedPort()
var listener = new TcpListener(IPAddress.Any, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
【讨论】:
不,它没有办法知道。 这并没有回答关于 HttpListener 类的问题。 我会说很公平,除了在方法结束时释放端口和启动 HttpListener 之间的竞争条件。 仅供参考,Google 在其所有 OAuth 代码和示例中使用您的代码 :) github.com/googlesamples/oauth-apps-for-windows/blob/… ) 竞争条件使我无法使用此选项。间歇性错误太多。【参考方案3】:我不相信这是可能的。 UriBuilder.Port 的文档指出,“如果未将端口指定为 URI 的一部分,...协议方案的默认端口值将用于连接到主机。”。
见https://msdn.microsoft.com/en-us/library/system.uribuilder.port(v=vs.110).aspx
【讨论】:
【参考方案4】:很遗憾,这是不可能的。正如 Richard Dingwall 已经建议的那样,您可以创建一个 TCP 侦听器并使用该端口。这种方法有两个可能的问题:
理论上可能会出现竞争条件,因为另一个 TCP 侦听器可能会在关闭此端口后使用它。 如果您不是以管理员身份运行,则需要分配前缀和端口组合以允许绑定到它。如果您没有管理员权限,这是无法管理的。从本质上讲,这会要求您需要以管理员权限运行 HTTP 服务器(这通常是个坏主意)。【讨论】:
我亲身体验了比赛条件。在我的例子中,我们有几个同时运行的测试,都使用了一个 TcpListener,它在创建一个 HttpListener 之前就停止了。每隔一段时间就会出现错误,因为该端口已在使用中。 BTW +1 用于实际回答问题。 :)【参考方案5】:由于您使用的是 HttpListener(以及 TCP 连接),因此您可以使用 IPGlobalProperties
对象的 GetActiveTcpListeners
方法获取活动 TCP 侦听器的列表,并检查它们的 Port
属性。
可能的解决方案可能如下所示:
private static bool TryGetUnusedPort(int startingPort, ref int port)
var listeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
for (var i = startingPort; i <= 65535; i++)
if (listeners.Any(x => x.Port == i)) continue;
port = i;
return true;
return false;
此代码将从startingPort
端口号开始查找第一个未使用的端口并返回true
。如果所有端口都已被占用(这不太可能),则该方法返回 false
。
还请记住,当其他进程在您之前占用找到的端口时,可能会发生竞争条件。
【讨论】:
有趣。该方法在我的机器上花费了大约 130 毫秒(第一次调用),这在某些情况下可能是不可接受的。这里仍然存在竞争条件,因此调用代码需要防御这种情况。另外,为什么使用ref
而不是out
?【参考方案6】:
我建议尝试Grapevine。它允许您在应用程序中嵌入 REST/HTTP 服务器。它包括一个RestCluster
类,可让您在一个地方管理所有RestServer
实例。
将每个实例设置为使用随机的开放端口号,如下所示:
using (var server = new RestServer())
// Grab the next open port (starts at 1)
server.Port = PortFinder.FindNextLocalOpenPort();
// Grab the next open port after 2000 (inclusive)
server.Port = PortFinder.FindNextLocalOpenPort(2000);
// Grab the next open port between 2000 and 5000 (inclusive)
server.Port = PortFinder.FindNextLocalOpenPort(200, 5000);
...
入门指南:https://sukona.github.io/Grapevine/en/getting-started.html
【讨论】:
【参考方案7】:这是来自Snooganz's answer 的答案。它避免了可用性测试和后续绑定之间的竞争条件。
public static bool TryBindListenerOnFreePort(out HttpListener httpListener, out int port)
// IANA suggested range for dynamic or private ports
const int MinPort = 49215;
const int MaxPort = 65535;
for (port = MinPort; port < MaxPort; port++)
httpListener = new HttpListener();
httpListener.Prefixes.Add($"http://localhost:port/");
try
httpListener.Start();
return true;
catch
// nothing to do here -- the listener disposes itself when Start throws
port = 0;
httpListener = null;
return false;
在我的机器上,这种方法平均需要 15 毫秒,这对于我的用例来说是可以接受的。希望这对其他人有帮助。
【讨论】:
这是完美的,谢谢。这是唯一真正可靠的方法,因为在找到端口和使用端口之间没有延迟,也不会尝试手动保留正在使用的端口列表。我将使用它来使我的测试更加可靠。 我只是想到了这里的一个小改进,实际上。按顺序遍历端口更有可能发生冲突,因为每个调用者都以相同的值开始。但是,每次在该范围内选择一个随机值可能会带来更一致的成功。不过,这是一个小问题。我仍然喜欢它是原子的。 :) @KenLyon 天真地随机选择一个端口的缺点是,如果没有可用的端口,该方法将永远不会返回。相反,您可以跟踪哪些已被检查,但这需要为 16,320 个可能的端口保留内存。每个端口使用一位需要 2,040 字节,这比我通常希望在堆栈上保留的要多。 确实,这将是最全面的解决方案。就我而言,这个问题一开始就很少见。它发生在作为我们构建的一部分的单元测试期间。我不想减慢测试速度,也不希望我们的测试服务器用完端口。作为妥协,我尝试了多达 50 个随机端口,然后失败。我希望这能让我在性能和可靠性之间取得平衡。以上是关于如何在 C# 中的随机端口上创建 HttpListener 类?的主要内容,如果未能解决你的问题,请参考以下文章