【问题标题】:How can I create an HttpListener class on a random port in C#?如何在 C# 中的随机端口上创建 HttpListener 类?
【发布时间】:2010-09-18 09:27:09
【问题描述】:

我想创建一个在内部为网页提供服务的应用程序,并且可以在同一台机器上的多个实例中运行。为此,我想创建一个 HttpListener 来侦听以下端口:

  1. 随机选择
  2. 目前未使用

基本上,我想要的是这样的:

mListener = new HttpListener();
mListener.Prefixes.Add("http://*:0/");
mListener.Start();
selectedPort = mListener.Port;

我怎样才能做到这一点?

【问题讨论】:

    标签: c# httplistener embeddedwebserver


    【解决方案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 个随机端口,然后失败。我希望这能让我在性能和可靠性之间取得平衡。
        猜你喜欢
        • 1970-01-01
        • 2020-11-29
        • 2014-01-28
        • 1970-01-01
        • 1970-01-01
        • 2011-02-18
        • 1970-01-01
        • 2018-07-27
        • 1970-01-01
        相关资源
        最近更新 更多