如何在 C# 中的随机端口上创建 HttpListener 类?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/223063/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 18:49:41  来源:igfitidea点击:

How can I create an HttpListener class on a random port in C#?

c#httplistenerembeddedwebserver

提问by

I would like to create an application that serves web pages internally and can be run in multiple instances on the same machine. To do so, I would like to create an HttpListenerthat listens on a port that is:

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

  1. Randomly selected
  2. Currently unused
  1. 随机选择
  2. 目前未使用

Essentially, what I would like is something like:

基本上,我想要的是:

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

How can I accomplish this?

我怎样才能做到这一点?

回答by Snooganz

How about something like this:

这样的事情怎么样:

    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;
    }

I'm not sure how you would find all of the ports that are in use on that machine, but you should get an exception if you try to listen on a port that is already being used, in which case the method will simply pick another port.

我不确定您将如何找到该机器上正在使用的所有端口,但是如果您尝试侦听已在使用的端口,您应该会得到一个异常,在这种情况下,该方法将简单地选择另一个港口。

回答by Richard Dingwall

TcpListener will find a random un-used port to listen on if you bind to port 0.

如果您绑定到端口 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;
}

回答by Andrew Rondeau

I do not believe this is possible. The documentation for UriBuilder.Port states, "If a port is not specified as part of the URI, ... the default port value for the protocol scheme will be used to connect to the host.".

我不相信这是可能的。UriBuilder.Port 的文档指出,“如果未将端口指定为 URI 的一部分,......协议方案的默认端口值将用于连接到主机。”。

See https://msdn.microsoft.com/en-us/library/system.uribuilder.port(v=vs.110).aspx

请参阅https://msdn.microsoft.com/en-us/library/system.uribuilder.port(v=vs.110).aspx

回答by Ramon de Klein

Unfortunately, this isn't possible. As Richard Dingwall already suggested, you could create a TCP listener and use that port. This approach has two possible problems:

不幸的是,这是不可能的。正如 Richard Dingwall 已经建议的那样,您可以创建一个 TCP 侦听器并使用该端口。这种方法有两个可能的问题:

  • In theory a race condition may occur, because another TCP listener might use this port after closing it.
  • If you're not running as an Administrator, then you need to allocate prefixes and port combinations to allow binding to it. This is impossible to manage if you don't have administrator privileges. Essentially this would impose that you need to run your HTTP server with administrator privileges (which is generally a bad idea).
  • 理论上可能会发生竞争条件,因为另一个 TCP 侦听器可能会在关闭该端口后使用该端口。
  • 如果您不是以管理员身份运行,则需要分配前缀和端口组合以允许绑定到它。如果您没有管理员权限,这是不可能管理的。本质上,这将强制您需要以管理员权限运行 HTTP 服务器(这通常是一个坏主意)。

回答by dodbrian

Since you are using an HttpListener (and therefore TCP connections) you can get a list of active TCP listeners using GetActiveTcpListenersmethod of the IPGlobalPropertiesobject and inspect their Portproperty.

由于您使用的是 HttpListener(以及 TCP 连接),因此您可以使用对象的GetActiveTcpListeners方法获取活动 TCP 侦听器的列表IPGlobalProperties并检查它们的Port属性。

The possible solution may look like this:

可能的解决方案可能如下所示:

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;
}

This code will find first unused port beginning from the startingPortport number and return true. In case all ports are already occupied (which is very unlikely) the method returns false.

此代码将从startingPort端口号开始查找第一个未使用的端口并返回true。如果所有端口都已被占用(这不太可能),该方法将返回false.

Also keep in mind the possibility of a race condition that may happen when some other process takes the found port before you do.

还要记住,当某些其他进程在您之前获取找到的端口时,可能会发生竞争条件。

回答by Scott Offen

I'd recommend trying Grapevine. It allows you embed a REST/HTTP server in your application. It includes a RestClusterclass that will allow you to manage all of your RestServerinstances in a single place.

我建议尝试Grapevine。它允许您在应用程序中嵌入 REST/HTTP 服务器。它包含一个RestCluster类,允许您RestServer在一个地方管理所有实例。

Set each instance to use a random, open port number like this:

将每个实例设置为使用随机的开放端口号,如下所示:

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);
    ...
}

Getting started guide: https://sukona.github.io/Grapevine/en/getting-started.html

入门指南:https: //sukona.github.io/Grapevine/en/getting-started.html

回答by Drew Noakes

Here's an answer, derived from Snooganz's answer. It avoids the race condition between testing for availability, and later binding.

这是一个来自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;
}

On my machine this method takes 15ms on average, which is acceptable for my use case. Hope this helps someone else.

在我的机器上,此方法平均需要 15 毫秒,这对我的用例来说是可以接受的。希望这对其他人有帮助。