2009-09-15 13 views
28

Sto provando a creare un SOA in cui i client possono eseguire query a esecuzione prolungata sul server e il server risponde utilizzando una richiamata.Determinazione del decesso del client nei contratti duplex WCF

Mi piacerebbe essere in grado di rilevare se il client si disconnette (attraverso l'arresto avviato dall'utente, un'eccezione non gestita o la perdita della connettività di rete) in modo che il server possa scegliere di annullare la richiesta costosa.

Sto testando una serie di casi di errore, ma non riesco a convincere alcuni gestori di eventi a sparare.

Casi di errore verificati: Uccisione del processo client Dopo la richiesta. Utilizzo di un programma come CurrPorts per chiudere la connessione TCP.

Codice di prova:

using System; 
using System.ServiceModel; 
using System.Threading; 

namespace WCFICommunicationObjectExperiments 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var binding = new NetTcpBinding(SecurityMode.None); 

      var serviceHost = new ServiceHost(typeof (Server)); 
      serviceHost.AddServiceEndpoint(typeof (IServer), binding, "net.tcp://localhost:5000/Server"); 
      serviceHost.Open(); 
      Console.WriteLine("Host is running, press <ENTER> to exit."); 
      Console.ReadLine(); 
     } 

    } 

    [ServiceContract(CallbackContract = typeof(IClient))] 
    public interface IServer 
    { 
     [OperationContract] 
     void StartProcessing(string Query); 
    } 

    public interface IClient 
    { 
     [OperationContract] 
     void RecieveResults(string Results); 
    } 

    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] 
    public class Server : IServer 
    { 

     public void StartProcessing(string Query) 
     { 
      Thread.Sleep(5000); 

      //Callback Channel 
      var clientCallback = OperationContext.Current.GetCallbackChannel<IClient>(); 
      var clientCallbackCommunicationObject = ((ICommunicationObject) clientCallback); 
      EventHandler faultedHandlerCallback = (o, s) => Console.WriteLine("Client Channel Faulted."); 
      EventHandler closedHandlerCallback = (o, s) => Console.WriteLine("Client Channel Closed."); 
      clientCallbackCommunicationObject.Faulted += faultedHandlerCallback; 
      clientCallbackCommunicationObject.Closed += closedHandlerCallback; 

      //Request Channel 
      var requestChannel = OperationContext.Current.Channel; 
      EventHandler faultedHandlerRequest = (o, s) => Console.WriteLine("Request Channel Faulted."); 
      EventHandler closedHandlerRequest = (o, s) => Console.WriteLine("Request Channel Closed."); 
      requestChannel.Faulted += faultedHandlerRequest; 
      requestChannel.Closed += closedHandlerRequest; 

      try 
      { 
       clientCallback.RecieveResults("42."); 
      } 
      catch (CommunicationObjectAbortedException ex) 
      { 
       Console.WriteLine("Client Aborted the connection"); 
      } 
      catch (CommunicationObjectFaultedException ex) 
      { 
       Console.WriteLine("Client Died."); 
      } 
      clientCallbackCommunicationObject.Faulted -= faultedHandlerCallback; 
      clientCallbackCommunicationObject.Faulted -= closedHandlerCallback; 
      requestChannel.Faulted -= faultedHandlerRequest; 
      requestChannel.Closed -= closedHandlerRequest; 
     } 
    } 

    public class ClientToTestStates : IClient 
    { 
     private IServer m_Server; 

     private readonly ManualResetEvent m_ReceivedEvent = new ManualResetEvent(false); 
     private readonly ManualResetEvent m_ChannelFaulted = new ManualResetEvent(false); 
     private readonly ManualResetEvent m_ChannelClosed = new ManualResetEvent(false); 

     public ClientToTestStates() 
     { 
      var binding = new NetTcpBinding(SecurityMode.None); 
      var channelFactory = new DuplexChannelFactory<IServer>(this, binding, new EndpointAddress("net.tcp://localhost:5000/Server")); 
      m_Server = channelFactory.CreateChannel(); 
      ((ICommunicationObject)m_Server).Open(); 
      ((ICommunicationObject)m_Server).Faulted += ChannelFaulted; 
      ((ICommunicationObject)m_Server).Closed += ChannelClosed; 

      m_Server.StartProcessing("What is the answer?"); 

      WaitHandle.WaitAny(new WaitHandle[] {m_ReceivedEvent, m_ChannelFaulted, m_ChannelClosed}); 
     } 

     void ChannelFaulted(object sender, EventArgs e) 
     { 
      m_ChannelFaulted.Set(); 
      Console.WriteLine("Channel Faulted."); 
     } 

     void ChannelClosed(object sender, EventArgs e) 
     { 
      m_ChannelClosed.Set(); 
      Console.WriteLine("Channel Closed."); 
     } 


     public void RecieveResults(string results) 
     { 
      m_ReceivedEvent.Set(); 
      Console.WriteLine("Recieved Results {0}", results); 
     } 
    } 
} 

Qual è la prassi migliore per gestire questo tipo di casi di insufficienza? Mi piacerebbe essere in grado di utilizzare la connessione TCP sottostante per rilevare alcune di queste cose.

+0

Avete provato ad accendere Affidabilità? TCP fornisce affidabilità point-to-point.
L'affidabilità dei messaggi (tramite WS-Reliability) garantisce l'affidabilità end-to-end.
E a sua volta credo che ti informeremo quando entrambe le parti "se ne andranno" senza tanti complimenti. Per i trasporti che lo supportano, è consigliabile attivare sempre l'affidabilità, anche se alcuni "goo" di rete potrebbero non supportarlo. –

risposta

14

Nel suo libro 'Programmazione Servizi WCF', Juval Lowy spiega che WCF non fornisce un meccanismo per la gestione dei callback dei servizi, e questo deve essere gestito esplicitamente dal servizio e dal client. Se il servizio tenta di richiamare una richiamata che è stata chiusa sul client, verrà lanciata una ObjectDisposedException sul canale di servizio.

Si consiglia di aggiungere un metodo Connetti e Disconnetti al contratto di servizio: poiché il callback deve essere fornito al servizio quando questi vengono chiamati, il servizio può gestire i callback dei client. Spetta quindi al client assicurarsi di chiamare Disconnetti quando non desidera più ricevere i callback dal servizio e il servizio deve gestire eventuali eccezioni durante il richiamo di callback al client.

+1

Grazie per l'informazione. Nel caso di un errore imprevisto del client, in cui non ci si può aspettare che Disconnect() venga richiamato, cosa si può fare per rilevare quell'errore sul lato server per liberare risorse preziose? – Sindhudweep

+1

dato che il sistema operativo sa che la connessione TCP non ha i pacchetti keep alive. Dovrebbe essere possibile per un server WCF sapere che il client è andato via. Quindi ci dovrebbe essere una risposta migliore di questo. Non so proprio cosa sia! –

+0

Il sistema operativo può utilizzare la connessione TCP. Nei miei test stavo usando l'associazione Net.tcp e stavo ricevendo eventi con errore di canale. Sfortunatamente se la connessione TCP è verso una macchina che funge da relay (usando l'inoltro remoto di SSH con binding globale) il SO non vede la connessione TCP chiusa (perché la connessione alla macchina relay non è stata effettivamente chiusa). Questo stava causando lo strano comportamento che ho osservato nel mio test. – Sindhudweep

12

provare questo per verificare se l'oggetto callback è ancora valido:

(((ICommunicationObject)myCallbackObject).State == CommunicationState.Opened) 

myCallbackObject in questo caso è l'oggetto attraverso il quale è possibile eseguire la richiamata, cioè quella di attuazione del contratto di callback

+5

questa soluzione non è consigliata perché il canale di richiamata può essere disturbato tra il momento in cui si controlla lo stato e l'ora in cui si fa qualcosa con il canale. – LxL

+2

Sì, dovresti comunque gestire eventuali eccezioni poiché non c'è davvero un modo per verificare con certezza. –

Problemi correlati