Как создать класс HttpListener на случайном порту в С#?
Я хотел бы создать приложение, которое обслуживает веб-страницы внутри себя и может быть запущено в нескольких экземплярах на одном компьютере. Для этого я хотел бы создать HttpListener
, который прослушивает порт, который:
- Случайно выбранный
- В настоящее время не используется
По сути, я бы хотел что-то вроде:
mListener = new HttpListener();
mListener.Prefixes.Add("http://*:0/");
mListener.Start();
selectedPort = mListener.Port;
Как я могу это сделать?
Ответы
Ответ 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 мс, что приемлемо для моего варианта использования. Надеюсь, это поможет кому-то еще.