如何在 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&lt;int&gt; 用于线性成员资格测试。就我个人而言,我会将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 类?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中的石头、纸、剪刀游戏中随机化数组中的变量? [复制]

如何生成保存到数组中的随机字符串? c# [重复]

如何从 C# 中的两个数组中获取两个随机字符串

Docker-端口映射

使用 C#,如何将变量设置为范围内的随机数? [复制]

C#使用随机数据创建对象[重复]