2008-10-21 14 views
26

Me gustaría crear una aplicación que sirva páginas web internamente y pueda ejecutarse en varias instancias en la misma máquina. Para ello, me gustaría crear una HttpListener que escucha en un puerto que es:¿Cómo puedo crear una clase HttpListener en un puerto aleatorio en C#?

  1. seleccionadas al azar
  2. Actualmente no se usa

En esencia, 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?

Respuesta

15

¿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 le gustaría encontrar todos los puertos que están en uso en ese equipo, pero debe hacerse una excepción si se intenta escuchar en un puerto que ya se está utilizando, en cuyo caso el método simplemente elegirá otro puerto.

+2

Es posible que desee considerar el uso de las constantes 'MinPort' /' MaxPort' en su lugar, [MSDN link] (http://msdn.microsoft.com/en-us/library/vstudio/system.net.ipendpoint_fields (v = vs.110) .aspx) –

+0

@JosephLennox, la constante 'MinPort' es cero. El valor mínimo dado en la respuesta es más adecuado. –

+0

@Snooganz, el bloque catch debería disponer probablemente de 'mListener'. Además, 'usedPorts' debería ser' Set ' para la prueba de membresía lineal. Personalmente, extendería 'usedPorts' dentro del método (o me deshago de él por completo) como si lo usaras en un sistema de larga duración que finalmente pudieras quedarte sin puertos, haciendo que el bucle' while' se ejecute para siempre, incluso si los puertos están disponibles a lo largo del tiempo –

37

TcpListener encontrará un puerto utilizado por la ONU al azar para escuchar en si enlaza 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; 
} 
+0

¿Esto tiene en cuenta los cortafuegos, si el puerto está bloqueado? –

+2

No, no hay forma de que lo sepa. –

+5

Esto no responde la pregunta sobre la clase HttpListener. – jnm2

0

Por desgracia, 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, puede producirse una condición de carrera, ya que otro oyente TCP podría usar este puerto después de cerrarlo.
  • Si no está ejecutándose como administrador, debe 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).
1

Puesto que usted está usando un HttpListener (y por lo tanto las conexiones TCP) se puede obtener una lista de los oyentes TCP activas utilizando GetActiveTcpListeners método del objeto IPGlobalProperties e inspeccionar su propiedad Port.

La posible solución puede tener este aspecto:

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 se encuentra primer puerto sin usar partir del número de puerto y volver startingPorttrue. 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.

+0

Interesante. Ese método tomó aproximadamente 130 ms en mi máquina (para la primera llamada) lo que podría ser inaceptable en algunos escenarios. Todavía hay una condición de carrera aquí, por lo que el código de llamada debe defenderse contra eso. Además, ¿por qué usar 'ref' y no' out'? –

1

Recomendaría intentar Grapevine. Le permite incrustar un servidor REST/HTTP en su aplicación. Incluye una clase RestCluster que le permitirá administrar todas sus instancias RestServer en un solo lugar.

Conjunto cada instancia de usar un número aleatorio, abierto el puerto de esta manera:

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 introducción: https://sukona.github.io/Grapevine/en/getting-started.html

0

Aquí es una respuesta, derivada de Snooganz's answer. 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 de este método toma en promedio 15 ms, lo que es aceptable para mi caso de uso. Espero que esto ayude a alguien más.

Cuestiones relacionadas