Rilevamento della morte del cliente nei contratti duplex WCF
-
07-07-2019 - |
Domanda
Sto cercando di creare una SOA in cui i client possano eseguire query di lunga durata sul server e il server risponde utilizzando un callback.
Mi piacerebbe essere in grado di rilevare se il client si disconnette (tramite arresto avviato dall'utente, eccezione non gestita o perdita della connettività di rete) in modo che il server possa scegliere di annullare la richiesta costosa.
Sto testando una varietà di casi di errore ma non riesco a far scattare determinati gestori di eventi.
Casi di errore testati: Interruzione del processo client Dopo la richiesta. Utilizzo di un programma come CurrPorts per chiudere la connessione TCP.
Codice test:
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 migliore pratica per gestire questo tipo di casi di fallimento? Mi piacerebbe essere in grado di utilizzare la connessione tcp sottostante per rilevare alcune di queste cose.
Soluzione
Nel suo libro "Programmazione dei servizi WCF", Juval Lowy spiega che WCF non fornisce un meccanismo per la gestione dei callback dei servizi, e questo deve essere gestito in modo esplicito dal servizio e dal client. Se il servizio tenta di richiamare un callback che è stato chiuso sul client, verrà lanciata una ObjectDisposedException sul canale del servizio.
Consiglia di aggiungere un metodo di connessione e disconnessione al contratto di servizio: poiché il callback deve essere fornito al servizio quando vengono chiamati, il servizio può gestire i callback client. Spetta quindi al client assicurarsi che chiami Disconnect quando non desidera più ricevere callback dal servizio e che il servizio deve gestire eventuali eccezioni quando invoca callback al client.
Altri suggerimenti
prova 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 il callback, ovvero quello che implementa il contratto di callback