2009-09-07 15 views
10

que tienen un tiempo difícil averiguar si hay una manera de manejar los posibles problemas de conectividad cuando se utiliza la clase HttpWebRequest de .NET para llamar a un servidor remoto (en concreto un servicio web REST). Según mis investigaciones, el comportamiento de la clase WebClient es el mismo, algo que se espera, ya que parece ofrecer solo una interfaz más simple para HttpWebRequest.HttpWebRequest ¿Cómo manejar el cierre (prematuro) de la conexión TCP subyacente?

Para fines de simulación, he escrito un servidor HTTP muy simple que no se comporta de acuerdo con el RFC HTTP 1.1. Lo que hace es aceptar una conexión de cliente, luego envía encabezados de HTTP 1.1 apropiados y un "¡Hola mundo!" de carga útil de vuelta al cliente y cierra el socket, el hilo de aceptar conexiones de cliente en el servidor se ve de la siguiente manera:

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

Desde los HTTP 1.1 estados RFC que HTTP 1.1 por defecto mantiene conexiones con vida y que un servidor debe enviar un encabezado de respuesta "Conexión: cerrar" si desea cerrar una conexión, este es un comportamiento inesperado para el lado del cliente. El cliente utiliza HttpWebRequest de la siguiente manera:

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

El método anterior se pone en cola a través de ThreadPool.QueueUserWorkItem (waitCallback, stateObject). ManualResetEvent se utiliza para controlar el comportamiento de cola para que no todo el grupo de subprocesos se llene con tareas en espera (ya que HttpWebRequest utiliza implícitamente subprocesos de trabajo porque funciona de forma asincrónica internamente para implementar la funcionalidad de tiempo de espera).

El problema con todo esto es que una vez que todas las conexiones del ServicePoint subyacente de HttpWebRequest se "agotan" (es decir, se cierran por el servidor remoto), no se abrirán nuevas. Tampoco importa si ConnectionLeaseTimeout de ServicePoint está configurado en un valor bajo (10 segundos). Una vez que el sistema entre en este estado, ya no funcionará correctamente porque no se reconectará automáticamente y todas las HttpWebRequests subsiguientes agotarán el tiempo de espera. Ahora la pregunta realmente es si hay una manera de resolver esto mediante la destrucción de alguna manera una ServicePoint bajo ciertas condiciones o cerrar conexiones underyling (yo no tenía ninguna suerte con ServicePoint.CloseConnectionGroup(), sin embargo, el método también es bastante indocumentado en términos de cómo para usarlo apropiadamente).

¿Alguien tiene alguna idea de cómo podría abordar este problema?

Respuesta

2

Este es un truco horrible, pero funciona. Llámalo periódicamente si notas que tus conexiones se estancan.

static public void SetIdle(object request) 
    { 
     MethodInfo getConnectionGroupLine = request.GetType().GetMethod("GetConnectionGroupLine", BindingFlags.Instance | BindingFlags.NonPublic); 
     string connectionName = (string)getConnectionGroupLine.Invoke(request, null); 

     ServicePoint servicePoint = ((HttpWebRequest)request).ServicePoint; 
     MethodInfo findConnectionGroup = servicePoint.GetType().GetMethod("FindConnectionGroup", BindingFlags.Instance | BindingFlags.NonPublic); 
     object connectionGroup; 
     lock (servicePoint) 
     { 
      connectionGroup = findConnectionGroup.Invoke(servicePoint, new object[] { connectionName, false }); 
     } 

     PropertyInfo currentConnections = connectionGroup.GetType().GetProperty("CurrentConnections", BindingFlags.Instance | BindingFlags.NonPublic); 
     PropertyInfo connectionLimit = connectionGroup.GetType().GetProperty("ConnectionLimit", BindingFlags.Instance | BindingFlags.NonPublic); 

     MethodInfo disableKeepAliveOnConnections = connectionGroup.GetType().GetMethod("DisableKeepAliveOnConnections", BindingFlags.Instance | BindingFlags.NonPublic); 

     if (((int)currentConnections.GetValue(connectionGroup, null)) == 
      ((int)connectionLimit.GetValue(connectionGroup, null))) 
     { 
      disableKeepAliveOnConnections.Invoke(connectionGroup, null); 
     } 

     MethodInfo connectionGoneIdle = connectionGroup.GetType().GetMethod("ConnectionGoneIdle", BindingFlags.Instance | BindingFlags.NonPublic); 
     connectionGoneIdle.Invoke(connectionGroup, null); 
    } 
1

Aquí está mi sugerencia. No lo he probado. Alter Reference.cs

protected override WebResponse GetWebResponse(WebRequest request) 
    { 
     try 
     { 
      return base.GetWebResponse(request); 
     } 
     catch (WebException) 
     { 
      HttpWebRequest httpWebRequest = request as HttpWebRequest; 
      if (httpWebRequest != null && httpWebRequest.ServicePoint != null) 
       httpWebRequest.ServicePoint.CloseConnectionGroup(httpWebRequest.ConnectionGroupName); 

      throw; 
     } 
    } 
6

La solución que se me ocurrió sobre la base de algunas de las ideas aquí es la gestión de las conexiones a mí mismo. Si se asigna un ConnectionGroupName exclusivo a una WebRequest (por ejemplo, Guid.NewGuid(). ToString()), se creará un nuevo grupo de conexión con una conexión en el ServicePoint para la solicitud. Tenga en cuenta que no hay más límites de conexión en este punto, ya que .NET limita por grupo de conexión en lugar de por ServicePoint, por lo que tendrá que manejarlo usted mismo. Deberá reutilizar los grupos de conexión para que las conexiones existentes con KeepAlive se vuelvan a utilizar, pero si se produce una excepción de WebException, el grupo de conexión de la solicitud se debe destruir ya que puede estar obsoleto. Algo como esto (crear una nueva instancia para cada nombre de host):

public class ConnectionManager { 
    private const int _maxConnections = 4; 

    private Semaphore _semaphore = new Semaphore(_maxConnections, _maxConnections); 
    private Stack<string> _groupNames = new Stack<string>(); 

    public string ObtainConnectionGroupName() { 
     _semaphore.WaitOne(); 
     return GetConnectionGroupName(); 
    } 

    public void ReleaseConnectionGroupName(string name) { 
     lock (_groupNames) { 
      _groupNames.Push(name); 
     } 
     _semaphore.Release(); 
    } 

    public string SwapForFreshConnection(string name, Uri uri) { 
     ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri); 
     servicePoint.CloseConnectionGroup(name); 
     return GetConnectionGroupName(); 
    } 

    private string GetConnectionGroupName() { 
     lock (_groupNames) { 
      return _groupNames.Count != 0 ? _groupNames.Pop() : Guid.NewGuid().ToString(); 
     } 
    } 
} 
Cuestiones relacionadas