在WCF双工协议中检测客户端断开连接

31

我正在尝试构建一个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连接来检测其中的一些问题。


你尝试过开启可靠性吗?TCP 提供点对点的可靠性。通过 WS-Reliability 实现的消息可靠性提供端到端的可靠性。另外,我相信当任意一方不礼貌地“离开”时,会通知你。对于支持它的传输,始终打开可靠性是最佳实践,但某些网络“胶水”可能不支持它。 - user729853
2个回答

17
在他的《Programming WCF Services》一书中,Juval Lowy解释说WCF不提供管理服务回调的机制,这必须由服务和客户端显式地管理。如果服务尝试调用在客户端已关闭的回调,则会在服务通道上抛出ObjectDisposedException异常。
他建议在服务契约中添加Connect和Disconnect方法-因为当调用它们时必须向服务提供回调,所以服务可以管理客户端回调。然后客户端需要确保在不再希望从服务接收回调时调用Disconnect,并且服务必须处理调用客户端回调时可能出现的任何异常。

3
谢谢提供信息。在客户端意外失效的情况下,如果无法预期调用Disconnect()函数,那么服务器端可以采取什么措施来及早检测故障并释放宝贵的资源? - Sindhudweep
1
鉴于操作系统知道TCP连接没有收到保持活动数据包,WCF服务器应该能够知道客户端已经离开。因此,应该有比这更好的答案。我只是不知道是什么! - Ian Ringrose
操作系统可以使用TCP连接。在我的测试中,我使用了Net.tcp绑定,并收到了通道故障事件。不幸的是,如果TCP连接是到充当中继的机器(使用SSH的远程转发和全局绑定),操作系统无法看到TCP连接关闭(因为与中继机器的连接实际上没有关闭)。这导致了我在测试中观察到的奇怪行为。 - Sindhudweep

12

尝试使用以下方法检查回调对象是否仍然有效:

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

在这种情况下,myCallbackObject是您可以通过执行回调来实现回调契约的对象。


5
不推荐使用此解决方案,因为在您检查状态和执行通道操作之间,回调通道可能会出现故障。 - LxL
3
是的,你仍然需要处理任何异常,因为没有一种确切的方法可以进行检查。 - Cedric Mamo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接