2009-09-07 16 views
10

Ho difficoltà a capire se esiste un modo per gestire potenziali problemi di connettività quando si utilizza la classe HttpWebRequest di .NET per chiamare un server remoto (in particolare un servizio Web REST). Dalle mie indagini il comportamento della classe WebClient è lo stesso, il che è in qualche modo previsto dal momento che sembra offrire solo un'interfaccia più semplice a HttpWebRequest.HttpWebRequest Come gestire (prematura) la chiusura della connessione TCP sottostante?

Per scopi di simulazione, ho scritto un server HTTP molto semplice che non si comporta in conformità con l'RFC HTTP 1.1. Quello che fa è che accetta una connessione client, quindi invia intestazioni HTTP 1.1 appropriate e un "Hello World!" payload al client e chiude la presa, il filo di accettare connessioni client sul lato server si presenta come segue:

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

Dal momento che le HTTP 1.1 stati RFC che HTTP 1.1 di default mantiene viva connessioni e che un server deve inviare un'intestazione di risposta "Connessione: Chiudi" se vuole chiudere una connessione, questo è un comportamento imprevisto per il lato client. Il client utilizza HttpWebRequest nel seguente modo:

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

il metodo di cui sopra è in coda tramite ThreadPool.QueueUserWorkItem (WaitCallback, stateObject). ManualResetEvent viene utilizzato per controllare il comportamento di accodamento in modo che non l'intero pool di thread venga riempito con attività di attesa (poiché HttpWebRequest utilizza implicitamente thread di lavoro perché funziona internamente in modo asincrono per implementare la funzionalità di timeout).

Il problema con tutto questo è che una volta che tutte le connessioni del ServicePoint sottostante di HttpWebRequest sono "esaurite" (cioè chiuse dal server remoto), non ne verranno aperte di nuove. Non importa se ConnectionLeaseTimeout di ServicePoint è impostato su un valore basso (10 secondi). Una volta che il sistema entra in questo stato, non funzionerà più correttamente perché non si ricollegherà automaticamente e tutti i successivi HttpWebRequests scadranno. Ora la domanda è davvero se c'è un modo per risolvere questo in qualche modo distruggendo un ServicePoint in determinate condizioni o chiudendo le connessioni underyling (non ho avuto fortuna con ServicePoint.CloseConnectionGroup() ancora, il metodo è anche piuttosto non documentato in termini di come per usarlo correttamente).

Qualcuno ha idea di come affrontare questo problema?

risposta

2

Questo è un trucco orribile, ma funziona. Chiamalo periodicamente se noti che le connessioni si bloccano.

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

Ecco il mio suggerimento. Non l'ho provato 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 soluzione mi è venuta basato su alcune delle idee qui è quello di gestire le connessioni me stesso. Se un oggetto ConnectionGroup univoco viene assegnato a una richiesta Web (ad esempio, Guid.NewGuid(). ToString()), un nuovo gruppo di connessione con una connessione verrà creato nel ServicePoint per la richiesta. Nota che non ci sono limiti di connessione a questo punto, dal momento che .NET limita per gruppo di connessioni piuttosto che per ServicePoint, quindi dovrai gestirlo da solo. Ti consigliamo di riutilizzare i gruppi di connessione in modo che le connessioni esistenti con KeepAlive vengano riutilizzate, ma se si verifica un'eccezione WebException, il gruppo di connessione della richiesta deve essere distrutto poiché potrebbe essere inattivo. Qualcosa di simile a questo (creare una nuova istanza per ciascun nome 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(); 
     } 
    } 
} 
Problemi correlati