Обнаружение смерти клиента в дуплексных контрактах WCF - PullRequest
31 голосов
/ 15 сентября 2009

Я пытаюсь создать SOA, где клиенты могут выполнять длительные запросы на сервере, а сервер отвечает обратным вызовом.

Я бы хотел быть в состоянии определить, отключается ли клиент (через отключение по инициативе пользователя, необработанное исключение или потеря сетевого подключения), чтобы сервер мог отменить дорогостоящий запрос.

Я тестирую различные случаи сбоев, но я не могу запустить определенные обработчики событий.

Проверенные случаи отказа: Уничтожение клиентского процесса после запроса. Использование программы типа CurrPorts для закрытия TCP-соединения.

Тестовый код:

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

Какая лучшая практика для обработки подобных сбоев? Я хотел бы иметь возможность использовать базовое соединение TCP для обнаружения некоторых из этих вещей.

Ответы [ 2 ]

17 голосов
/ 15 сентября 2009

В своей книге «Программирование служб WCF» Джувал Лоуи объясняет, что WCF не предоставляет механизм управления обратными вызовами службы, и этим должен управлять сервис и клиент в явном виде. Если служба пытается вызвать обратный вызов, который был закрыт на клиенте, исключение ObjectDisposedException будет сгенерировано на канале службы.

Он рекомендует добавить метод Connect и Disconnect к контракту на обслуживание - поскольку обратный вызов должен быть предоставлен службе при ее вызове, служба может управлять обратными вызовами клиента. Затем клиент должен убедиться, что он вызывает Disconnect, когда он больше не хочет получать обратные вызовы от службы, и служба должна обрабатывать любые исключения при вызове обратных вызовов для клиента.

12 голосов
/ 26 октября 2012

попробуйте это, чтобы проверить, действителен ли объект обратного вызова:

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

myCallbackObject в данном случае - это объект, через который вы можете выполнить обратный вызов, то есть объект, реализующий контракт обратного вызова

...