2008-10-21 9 views
26

Chciałbym stworzyć aplikację, która obsługuje strony internetowe wewnętrznie i może być uruchamiana w wielu instancjach na tym samym komputerze. Aby to zrobić, chciałbym stworzyć HttpListener który nasłuchuje na porcie, który jest:Jak utworzyć klasę HttpListener na losowym porcie w C#?

  1. Losowo wybrane
  2. Obecnie nieużywane

Zasadniczo, co chciałbym coś takiego jak:

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

Jak mogę to zrobić?

Odpowiedz

15

Jak o coś takiego:

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

Nie jestem pewien, w jaki sposób można znaleźć wszystkie porty, które są używane na tym komputerze, ale należy uzyskać wyjątek, jeśli starają się słuchać na port, który jest już używany, w takim przypadku metoda po prostu wybierze inny port.

+2

Można rozważyć użycie stałych 'MinPort' /' MaxPort', [łącze MSDN] (http://msdn.microsoft.com/en-us/library/vstudio/system.net.ipendpoint_fields (v = vs.110) .aspx) –

+0

@JosephLennox, stała 'MinPort' wynosi zero. Minimalna wartość podana w odpowiedzi jest bardziej odpowiednia. –

+0

@Snooganz, blok catch prawdopodobnie powinien rozporządzać 'mListener'. Także 'usedPorts' powinien być' Setem 'dla testu członkostwa liniowego. Osobiście użyłbym 'usedPorts' w metodzie (lub całkowicie się jej pozbyłem), jak gdybyś używał tego w bardzo długim systemie, w którym mógłbyś w końcu zabraknąć portów, przez co pętla' while' działała wiecznie, nawet jeśli porty stają się dostępne w miarę upływu czasu. –

37

TcpListener znajdzie losową un-used portu do słuchania na jeśli wiążą się z portu 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

Czy uwzględnia to zapory ogniowe, jeśli port jest zablokowany? –

+2

Nie, nie ma sposobu, aby to wiedzieć. –

+5

Nie odpowiada to na pytanie dotyczące klasy HttpListener. – jnm2

0

Niestety, nie jest to możliwe. Jak już zasugerował Richard Dingwall, możesz stworzyć odbiornik TCP i użyć tego portu. To podejście ma dwa możliwe problemy:

  • Teoretycznie może wystąpić warunek wyścigu, ponieważ inny odbiornik TCP może użyć tego portu po jego zamknięciu.
  • Jeśli nie pracujesz jako administrator, musisz przydzielić prefiksy i kombinacje portów, aby umożliwić powiązanie z nimi. Nie można tego zarządzać, jeśli nie masz uprawnień administratora. Zasadniczo wymagałoby to uruchomienia serwera HTTP z uprawnieniami administratora (co jest generalnie złym pomysłem).
1

Ponieważ używasz HttpListener (a więc połączenia TCP) można uzyskać listę aktywnych słuchaczy TCP używając GetActiveTcpListeners metodę obiektu IPGlobalProperties i wglądu do swoich Port nieruchomości.

Możliwe rozwiązanie może wyglądać następująco:

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

Kod ten znajdzie pierwszy nieużywany port począwszy od numeru portu startingPort i powrócić true. W przypadku, gdy wszystkie porty są już zajęte (co jest bardzo mało prawdopodobne), metoda zwraca false.

Należy również pamiętać o możliwym stanie wyścigu, który może się zdarzyć, gdy jakiś inny proces zabierze znaleziony port, zanim to zrobisz.

+0

Interesujące. Ta metoda zajęła około 130 ms na moim komputerze (w przypadku pierwszego połączenia), co może być niedopuszczalne w niektórych scenariuszach. Wciąż jest tutaj wyścigowy warunek, więc kod telefoniczny musi się przed nim bronić. Po co używać 'ref', a nie' out'? –

1

Polecam wypróbowanie Grapevine. Umożliwia osadzenie serwera REST/HTTP w aplikacji. Obejmuje klasę RestCluster, która pozwala zarządzać wszystkimi instancjami RestServer w jednym miejscu.

Set każde wystąpienie w użyciu losowego, otwarty numer portu tak:

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

podręcznik Rozpoczęcie pracy: https://sukona.github.io/Grapevine/en/getting-started.html

0

Oto odpowiedź, pochodzące z Snooganz's answer. Pozwala uniknąć warunków wyścigu między testowaniem pod kątem dostępności, a późniejszym wiązaniem.

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

Na mojej maszynie ta metoda zajmuje średnio 15ms, co jest dopuszczalne w moim przypadku użycia. Mam nadzieję, że to pomoże komuś innemu.

Powiązane problemy