Подтвердить что ты не робот

Как создать класс HttpListener на случайном порту в С#?

Я хотел бы создать приложение, которое обслуживает веб-страницы внутри себя и может быть запущено в нескольких экземплярах на одном компьютере. Для этого я хотел бы создать HttpListener, который прослушивает порт, который:

  • Случайно выбранный
  • В настоящее время не используется

По сути, я бы хотел что-то вроде:

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

Как я могу это сделать?

4b9b3361

Ответ 1

TcpListener найдет случайный неиспользуемый порт для прослушивания, если вы привязываетесь к порту 0.

public static int GetRandomUnusedPort()
{
    var listener = new TcpListener(IPAddress.Any, 0);
    listener.Start();
    var port = ((IPEndPoint)listener.LocalEndpoint).Port;
    listener.Stop();
    return port;
}

Ответ 2

Как насчет чего-то вроде этого:

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

Я не уверен, как вы найдете все порты, которые используются на этой машине, но вы должны получить исключение, если попытаетесь прослушивать порт, который уже используется, и в этом случае метод будет просто выберите другой порт.

Ответ 3

Поскольку вы используете HttpListener (и, следовательно, TCP-соединения), вы можете получить список активных прослушивателей TCP с использованием метода GetActiveTcpListeners объекта IPGlobalProperties и проверить их свойство 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.

Также помните о возможности состояния гонки, которое может произойти, когда какой-либо другой процесс принимает найденный порт до того, как вы это сделаете.

Ответ 4

Я бы рекомендовал попробовать 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

Ответ 5

Я не считаю, что это возможно. В документации для UriBuilder.Port говорится: "Если порт не указан как часть URI,... для подключения к хосту будет использоваться значение порта по умолчанию для схемы протокола.".

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

Ответ 6

К сожалению, это невозможно. Как уже сказал Ричард Динголл, вы можете создать прослушиватель TCP и использовать этот порт. Этот подход имеет две возможные проблемы:

  • Теоретически может возникнуть условие гонки, потому что другой приемник TCP может использовать этот порт после его закрытия.
  • Если вы не работаете как администратор, вам необходимо выделить префиксы и комбинации портов, чтобы разрешить привязку к нему. Это невозможно, если у вас нет прав администратора. По сути, это будет означать, что вам нужно запустить свой HTTP-сервер с правами администратора (что, как правило, плохая идея).

Ответ 7

Здесь ответ, полученный из ответа Snooganz. Это позволяет избежать состояния гонки между тестированием на наличие и последующим привязкой.

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 мс, что приемлемо для моего варианта использования. Надеюсь, это поможет кому-то еще.