2012-01-27 27 views
11

Mam usługę Windows .NET, która spawnuje wątek, który zasadniczo działa jak HttpListener. To działa poprawnie w trybie synchronicznym przykład ...Obsługa wielu żądań za pomocą C# HttpListener

private void CreateLListener() 
{ 
    HttpListenerContext context = null; 
    HttpListener listener = new HttpListener(); 
    bool listen = true; 

    while(listen) 
    { 
     try 
     { 
      context = listener.GetContext(); 
     } 
     catch (...) 
     { 
      listen = false; 
     } 
     // process request and make response 
    } 
} 

Problem mam teraz jest mi to potrzebne do pracy z wieloma wnioskami i ich odpowiedzi na jednoczesne lub przynajmniej w nakładany sposób.

Aby wyjaśnić dalej - klient jest aplikacją odtwarzacza multimediów, która rozpoczyna się od żądania pliku multimedialnego z właściwością nagłówka żądania Range bytes=0-. O ile wiem, robi to, aby ustalić, co to jest kontener mediów.

Po przeczytaniu "kawałka" (lub jeśli został odczytany wystarczająco, aby potwierdzić rodzaj nośnika), wysyła kolejne żądanie (z innego numeru gniazda klienta) z numerem Range bytes=X-Y. W tym przypadku Y to długość treści zwrócona w pierwszej odpowiedzi, a X jest 250000 bajtów mniejsza od tej (wykryta przy użyciu IIS jako testu). Na tym etapie uzyskuje się ostatni "kawałek", aby sprawdzić, czy może uzyskać znacznik czasu mediów, aby zmierzyć długość.

Po przeczytaniu tego, wysyła kolejne żądanie z numerem Range bytes=0- (z innego numeru gniazda), aby rozpocząć przesyłanie strumieniowe pliku multimedialnego prawidłowo.

W każdym momencie, jeśli użytkownik klienta wykona operację "pominięcie", wówczas wysyła kolejne żądanie (z innego numeru gniazda) z Range bytes=Z-, gdzie Z jest miejscem do przeskoczenia do pliku multimedialnego.

Nie jestem zbyt dobry z materiałami HTTP, ale z tego co wiem, muszę użyć wielu wątków, aby obsłużyć każde żądanie/odpowiedź, jednocześnie pozwalając, aby oryginalny HttpListener powrócił do słuchania. Zrobiłem dużo wyszukiwania, ale nie mogę znaleźć modelu, który wydaje się pasować.

EDIT:

Uznanie i wdzięczność Rick Strahl na poniższym przykładzie, który udało mi się dostosować do moich potrzeb ...

Add a Web Server to your .NET 2.0 app with a few lines of code

Odpowiedz

12

Jeśli potrzebujesz prostszej alternatywy do BeginGetContext, możesz po prostu umieszczać w kolejce zadania w ThreadPool, zamiast wykonywać je w głównym wątku.Takie jak:

private void CreateLListener() { 
    //.... 
    while(true) { 
     ThreadPool.QueueUserWorkItem(Process, listener.GetContext());  
    } 
} 
void Process(object o) { 
    var context = o as HttpListenerContext; 
    // process request and make response 
} 
+2

Doskonały sposób na zrobienie tego, ale radzenie sobie z programowaniem Async będzie lepiej serwerować –

10

Musisz użyć asynchronicznie metoda, aby móc przetwarzać wiele żądań. Więc używałbyś metod e BeginGetContext i EndGetContext.

Spójrz na here.

Synchroniczne model jest odpowiedni, czy aplikacja powinna blokować czekając na żądanie klienta, a jeśli chcesz, aby przetwarzać tylko jeden * żądanie naraz *. Korzystając z modelu synchronicznego, wywołaj metodę GetContext , która czeka, aż klient wyśle ​​żądanie. Ta metoda zwraca obiekt o wartości HttpListenerContext do przetwarzania w momencie wystąpienia.

+0

asynchroniczny działa inaczej synchronizować (oczywiście). Wezwanie do rozpoczęcia natychmiast się zwróci, więc musisz sobie z tym poradzić, dlatego większość aplikacji ma jakiś rodzaj waitone. To jednak nie blokuje. Wywołanie, które zostało określone podczas wywoływania begin, zostanie wykonane na nowym wątku. Tam zadzwoń do końca, a następnie zacznij ponownie słuchać, a potem wykonuj swoją pracę. nowe żądania są obsługiwane w nowych wątkach, dopóki nie zdecydujesz się przestać słuchać. –

+2

@MisterSquonk Cieszę się, że to zadziałało. Nie dajemy tu ryb, ale uczymy łowienia ryb. :) – Aliostad

5

Jeśli jesteś tu z przyszłości i stara się obsłużyć wiele jednoczesnych żądań z pojedynczej nici za pomocą async/czekają ..

public async Task Listen(string prefix, int maxConcurrentRequests, CancellationToken token) 
{ 
    HttpListener listener = new HttpListener(); 
    listener.Prefixes.Add(prefix); 
    listener.Start(); 

    var requests = new HashSet<Task>(); 
    for(int i=0; i < maxConcurrentRequests; i++) 
     requests.Add(listener.GetContextAsync()); 

    while (!token.IsCancellationRequested) 
    { 
     Task t = await Task.WhenAny(requests); 
     requests.Remove(t); 

     if (t is Task<HttpListenerContext>) 
     { 
      var context = (t as Task<HttpListenerContext>).Result; 
      requests.Add(ProcessRequestAsync(context)); 
      requests.Add(listener.GetContextAsync()); 
     } 
    } 
} 

public async Task ProcessRequestAsync(HttpListenerContext context) 
{ 
    ...do stuff... 
} 
+1

Jeśli zależy Ci na wyjątkach od ProcessRequestAsync i powinieneś się tym przejmować, nie zapomnij wywołać t.Wait() w innej gałęzi. Podsumowując, podoba mi się to podejście +1. – frast

+0

Kolejnym problemem jest to, że warunek while jest sprawdzany tylko wtedy, gdy zakończy się jedno z zadań WhenAny. – frast

+0

Czy możesz wyjaśnić, dlaczego sprawdzanie tylko kiedy jest problem @frast? – LightLabyrinth

Powiązane problemy