Es fällt mir schwer herauszufinden, ob es eine Möglichkeit gibt, mit möglichen Konnektivitätsproblemen umzugehen, wenn .NETs HttpWebRequest-Klasse verwendet wird, um einen Remote-Server (speziell einen REST-Webdienst) aufzurufen. Aus meinen Untersuchungen ist das Verhalten der WebClient-Klasse das gleiche, was etwas erwartet wird, da es scheinbar nur eine einfachere Schnittstelle zu dem HttpWebRequest bietet.HttpWebRequest Wie behandelt (vorzeitige) Schließung der zugrunde liegenden TCP-Verbindung?
Zu Simulationszwecken habe ich einen sehr einfachen HTTP-Server geschrieben, der sich nicht nach dem HTTP 1.1-RFC verhält. Was es tut ist, dass es eine Client-Verbindung akzeptiert, dann sendet es entsprechende HTTP 1.1-Header und eine "Hallo Welt!" Nutzlast an den Client zurück und schließt die Buchse, der Thread-Client-Verbindungen auf der Serverseite zu akzeptieren sieht wie folgt aus:
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();
}
}
}
Da die HTTP 1.1 RFC Zustände, die HTTP 1.1 von Standardverbindungen alive und dass ein Server hält muss senden ein Antwortheader "Verbindung: Schließen", wenn er eine Verbindung schließen möchte, ist dies ein unerwartetes Verhalten für die Clientseite. Der Client verwendet HttpWebRequest in folgenden Weise:
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();
}
}
Das obige Verfahren über ThreadPool.QueueUserWorkItem (waitCallback, stateobject) ansteht. Das ManualResetEvent wird verwendet, um das Warteschlangenverhalten zu steuern, so dass nicht der gesamte Thread-Pool mit wartenden Tasks gefüllt wird (da die HttpWebRequest Worker-Threads implizit verwendet, da sie intern asynchron zur Implementierung der Timeout-Funktionalität funktioniert).
Das Problem bei all dem ist, dass, sobald alle Verbindungen der zugrunde liegenden Servicepoint des HttpWebRequest werden „verbraucht“ (das heißt von dem Remote-Server geschlossen), wird es keine neue erschlossen werden. Es spielt auch keine Rolle, ob ConnectionLeaseTimeout des ServicePoint auf einen niedrigen Wert (10 Sekunden) gesetzt ist. Sobald das System in diesen Zustand versetzt wird, funktioniert es nicht mehr ordnungsgemäß, da es sich nicht automatisch wieder verbindet und alle nachfolgenden HttpWebRequests eine Zeitüberschreitung aufweisen. Jetzt ist die Frage wirklich, ob es einen Weg gibt, dies zu lösen, indem man unter bestimmten Bedingungen einen ServicePoint irgendwie zerstört oder untergeordnete Verbindungen schließt (Ich hatte mit ServicePoint.CloseConnectionGroup() noch kein Glück, aber die Methode ist auch ziemlich undokumentiert um es richtig zu benutzen).
Hat jemand eine Idee, wie ich dieses Problem angehen könnte?