para - ¿Cómo puedo crear una clase HttpListener en un puerto aleatorio en C#?
generar numeros aleatorios en c# sin repetir (7)
TcpListener encontrará un puerto aleatorio no utilizado para escuchar si te vinculas al puerto 0.
public static int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Any, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
Me gustaría crear una aplicación que sirva páginas web internamente y pueda ejecutarse en múltiples instancias en la misma máquina. Para hacerlo, me gustaría crear un HttpListener
que escuche en un puerto que sea:
- Seleccionado aleatoriamente
- No utilizado actualmente
Esencialmente, lo que me gustaría es algo así como:
mListener = new HttpListener();
mListener.Prefixes.Add("http://*:0/");
mListener.Start();
selectedPort = mListener.Port;
¿Cómo puedo lograr esto?
Qué tal algo como esto:
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;
}
No estoy seguro de cómo encontraría todos los puertos que están en uso en esa máquina, pero debería obtener una excepción si intenta escuchar en un puerto que ya está siendo utilizado, en cuyo caso el método simplemente elegirá otro Puerto.
No creo que esto sea posible. La documentación para UriBuilder.Port establece, "Si un puerto no se especifica como parte del URI, ... el valor de puerto predeterminado para el esquema de protocolo se utilizará para conectarse al host".
Consulte https://msdn.microsoft.com/en-us/library/system.uribuilder.port(v=vs.110).aspx
Lamentablemente, esto no es posible. Como Richard Dingwall ya sugirió, podría crear un oyente TCP y usar ese puerto. Este enfoque tiene dos posibles problemas:
- En teoría, una condición de carrera puede ocurrir, porque otro oyente TCP podría usar este puerto después de cerrarlo.
- Si no está ejecutándose como un administrador, entonces necesita asignar prefijos y combinaciones de puertos para permitir el enlace. Esto es imposible de administrar si no tiene privilegios de administrador. Esencialmente, esto impondría que necesita ejecutar su servidor HTTP con privilegios de administrador (que generalmente es una mala idea).
Como está utilizando un HttpListener (y, por lo tanto, conexiones TCP) puede obtener una lista de escuchas TCP activas utilizando el método GetActiveTcpListeners
del objeto IPGlobalProperties
e inspeccionar su propiedad Port
.
La posible solución puede verse así:
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;
}
Este código encontrará el primer puerto no utilizado comenzando desde el número de puerto startingPort
y devolverá true
. En caso de que todos los puertos ya estén ocupados (lo cual es muy poco probable), el método devuelve false
.
También tenga en cuenta la posibilidad de una condición de carrera que puede ocurrir cuando algún otro proceso toma el puerto encontrado antes que usted.
Recomiendo probar Grapevine . Le permite incrustar un servidor REST / HTTP en su aplicación. Incluye una clase RestCluster
que le permitirá administrar todas sus instancias de RestServer
en un solo lugar.
Establezca cada instancia para usar un número de puerto abierto al azar como este:
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);
...
}
Guía de inicio: https://sukona.github.io/Grapevine/en/getting-started.html
Aquí hay una respuesta, derivada de la respuesta de Snooganz . Evita la condición de carrera entre las pruebas de disponibilidad y el enlace posterior.
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;
}
En mi máquina, este método toma 15ms en promedio, lo cual es aceptable para mi caso de uso. Espero que esto ayude a alguien más.