Mam trudności z ustaleniem, czy istnieje sposób na rozwiązanie potencjalnych problemów z łącznością podczas korzystania z klasy HttpWebRequest .NET do wywoływania zdalnego serwera (w szczególności usługi sieciowej REST). Z moich badań zachowanie klasy WebClient jest takie samo, co jest nieco oczekiwane, ponieważ wydaje się, że oferuje tylko prostszy interfejs do HttpWebRequest.HttpWebRequest Jak obsłużyć (przedwczesne) zamknięcie podstawowego połączenia TCP?
Dla celów symulacji napisałem bardzo prosty serwer HTTP, który nie zachowuje się zgodnie ze specyfikacją HTTP 1.1 RFC. To, co robi, to akceptuje połączenie klienta, a następnie wysyła odpowiednie nagłówki HTTP 1.1 i "Hello World!" ładunek z powrotem do klienta i zamyka gniazdo, wątek przyjmowania połączeń klienckich po stronie serwera wygląda następująco:
private const string m_defaultResponse = "<html><body><h1>Hello World!</h1></body></html>";
private void Listen()
{
while (true)
{
using (TcpClient clientConnection = m_listener.AcceptTcpClient())
{
NetworkStream stream = clientConnection.GetStream();
StringBuilder httpData = new StringBuilder("HTTP/1.1 200 OK\r\nServer: ivy\r\nContent-Type: text/html\r\n");
httpData.AppendFormat("Content-Length: {0}\r\n\r\n", m_defaultResponse.Length);
httpData.AppendFormat(m_defaultResponse);
Thread.Sleep(3000); // Sleep to simulate latency
stream.Write(Encoding.ASCII.GetBytes(httpData.ToString()), 0, httpData.Length);
stream.Close();
clientConnection.Close();
}
}
}
Ponieważ HTTP 1.1 RFC państw, które HTTP 1.1 domyślnie przechowuje połączenia żyje i że serwer musi wysłać nagłówek odpowiedzi "Połączenie: Zamknij", jeśli chce zamknąć połączenie, jest to nieoczekiwane zachowanie po stronie klienta. Klient używa HttpWebRequest w następujący sposób:
private static void SendRequest(object _state)
{
WebResponse resp = null;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://192.168.0.32:7070/asdasd");
request.Timeout = 50 * 1000;
DateTime requestStart = DateTime.Now;
resp = request.GetResponse();
TimeSpan requestDuration = DateTime.Now - requestStart;
Console.WriteLine("OK. Request took: " + (int)requestDuration.TotalMilliseconds + " ms.");
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.Timeout)
{
Console.WriteLine("Timeout occurred");
}
else
{
Console.WriteLine(ex);
}
}
finally
{
if (resp != null)
{
resp.Close();
}
((ManualResetEvent)_state).Set();
}
}
Powyższa metoda jest w kolejce przez ThreadPool.QueueUserWorkItem (waitCallback, stateObject). ManualResetEvent służy do kontrolowania zachowania w kolejce, tak aby cała pula wątków nie została wypełniona zadaniami oczekującymi (ponieważ HttpWebRequest niejawnie używa wątków roboczych, ponieważ wewnętrznie funkcjonuje asynchronicznie w celu implementacji funkcji limitu czasu).
Problem z tym wszystkim polega na tym, że gdy wszystkie połączenia podstawowego ServicePointa HttpWebRequest zostaną "zużyte" (tj. Zamknięte przez serwer zdalny), nie zostaną otwarte żadne nowe. Nie ma również znaczenia, czy wartość ConnectionLeaseTimeout ServicePoint jest ustawiona na niską wartość (10 sekund). Gdy system wejdzie w ten stan, nie będzie działał poprawnie, ponieważ nie łączy się ponownie automatycznie i wszystkie kolejne HttpWebRequests upłyną. Teraz naprawdę chodzi o to, czy istnieje sposób na rozwiązanie tego problemu, niszcząc ServicePoint pod pewnymi warunkami lub zamykając połączenia underylingowe (nie miałem jeszcze szczęścia w ServicePoint.CloseConnectionGroup(), ale metoda ta jest również nieudokumentowana pod względem sposobu właściwie go używać).
Czy ktoś ma pojęcie, jak mogę podejść do tego problemu?