2009-09-15 13 views
28

Ich versuche, eine SOA zu erstellen, bei der Clients lange laufende Abfragen auf dem Server ausführen können und der Server mit einem Rückruf reagiert.Erkennen von Client-Death in WCF-Duplex-Verträgen

Ich möchte in der Lage zu erkennen, wenn der Client die Verbindung trennt (durch Benutzer initiiert Herunterfahren, unbehandelt Ausnahme oder Verlust der Netzwerkverbindung), so dass der Server die teure Anfrage abbrechen kann.

Ich teste eine Vielzahl von Fehlerfällen, aber ich kann nicht scheinen bestimmte Ereignishandler zu feuern.

Geprüfte Fehlerfälle: Töten des Client-Prozesses Nach der Anfrage. Verwenden Sie ein Programm wie CurrPorts, um die TCP-Verbindung zu schließen.

Testcode:

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

Was ist die beste Praxis, diese Art von Fehlerfällen zu behandeln? Ich möchte in der Lage sein, die zugrunde liegende TCP-Verbindung zu verwenden, um einige dieser Dinge zu erkennen.

+0

Sie haben versuchte, Zuverlässigkeit zu aktivieren? TCP bietet Punkt-zu-Punkt-Zuverlässigkeit.
Nachrichtenzuverlässigkeit (über WS-Zuverlässigkeit) bietet durchgängige Zuverlässigkeit.
Und im Gegenzug glaube ich, dass Sie benachrichtigt werden, wenn eine Seite "ohne weiteres" weggeht. Für Transporte, die dies unterstützen, ist es die beste Vorgehensweise, die Zuverlässigkeit immer zu aktivieren, obwohl einige Netzwerke "Goo" sie möglicherweise nicht unterstützen. –

Antwort

14

In seinem Buch 'Programming WCF Services' erklärt Juval Lowy, dass WCF keine Mechanismen für die Verwaltung von Service-Callbacks bietet und diese explizit vom Service und vom Client verwaltet werden müssen. Wenn der Dienst versucht, einen Rückruf aufzurufen, der auf dem Client geschlossen wurde, wird eine ObjectDisposedException auf dem Servicekanal ausgelöst.

Er empfiehlt, dem Dienstvertrag eine Connect- und Disconnect-Methode hinzuzufügen - da der Rückruf dem Dienst bereitgestellt werden muss, wenn diese aufgerufen werden, kann der Dienst Client-Rückrufe verwalten. Es ist dann Sache des Clients sicherzustellen, dass es Disconnect aufruft, wenn es keine Callbacks mehr von dem Service erhalten möchte, und der Service muss jegliche Exceptions behandeln, wenn er Callbacks zum Client aufruft.

+1

Danke für die Informationen. Im Falle eines unerwarteten Client-Fehlers, bei dem nicht erwartet werden konnte, dass Disconnect() aufgerufen wird, was getan werden kann, um diesen Fehler früh auf der Serverseite zu erkennen, um wertvolle Ressourcen freizusetzen? – Sindhudweep

+1

vorausgesetzt das OS weiß, dass die TCP-Verbindung nicht die Keep-Alive-Pakete erhalten hat. Es sollte möglich sein, dass ein WCF-Server weiß, dass der Client weg ist. Also sollte es eine bessere Antwort als diese geben. Ich weiß nur nicht was es ist! –

+0

Das Betriebssystem kann die TCP-Verbindung verwenden. In meinen Tests verwendete ich die Net.tcp-Bindung und ich empfing Kanalfehler Ereignisse. Wenn die tcp-Verbindung zu einer Maschine ist, die als Relay dient (mit der Remote-Weiterleitung von SSH mit globaler Bindung), sieht das Betriebssystem die TCP-Verbindung leider nicht geschlossen (weil die Verbindung zur Relay-Maschine nicht tatsächlich geschlossen wurde). Dies verursachte das seltsame Verhalten, das ich in meinem Test beobachtete. – Sindhudweep

12

dies versuchen, wenn die Callback-Objekt zu überprüfen, noch gültig ist:

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

myCallbackObject in diesem Fall ist das Objekt, durch die Sie den Rückruf durchführen kann, dh das man den Vertrag Rückruf Implementierung

+5

Diese Lösung wird nicht empfohlen, da der Rückrufkanal zwischen dem Zeitpunkt der Statusüberprüfung und dem Zeitpunkt, zu dem Sie mit dem Kanal arbeiten, gestört sein kann. – LxL

+2

Ja, Sie sollten immer noch mit Ausnahmen umgehen, da es nicht wirklich möglich ist, dies zu überprüfen. –