在 WCF 双工合同中检测客户端死亡

Posted

技术标签:

【中文标题】在 WCF 双工合同中检测客户端死亡【英文标题】:Detecting Client Death in WCF Duplex Contracts 【发布时间】:2010-11-28 12:20:30 【问题描述】:

我正在尝试构建一个 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)提供端到端的可靠性。 反过来,我相信会在任何一方毫不客气地“离开”时通知您。对于支持它的传输,最好始终打开可靠性,尽管某些网络“goo”可能不支持它 【参考方案1】:

在他的“Programming WCF Services”一书中,Juval Lowy 解释说 WCF 不提供管理服务回调的机制,这必须由服务和客户端明确管理。如果服务尝试调用客户端已关闭的回调,则会在服务通道上抛出 ObjectDisposedException。

他建议在服务契约中添加一个 Connect 和 Disconnect 方法 - 因为回调必须在调用这些方法时提供给服务,所以服务可以管理客户端回调。然后由客户端确保在不再希望接收来自服务的回调时调用 Disconnect,并且服务必须在向客户端调用回调时处理任何异常。

【讨论】:

感谢您的信息。如果发生意外的客户端故障(无法预期 Disconnect() 会被调用),可以采取哪些措施在服务器端及早检测到该故障以释放宝贵的资源? 假设操作系统知道 TCP 连接没有得到保持活动的数据包。 WCF 服务器应该有可能知道客户端已经离开。所以应该有比这个更好的答案。我只是不知道它是什么! 操作系统可以使用 TCP 连接。在我的测试中,我使用的是 Net.tcp 绑定并且我正在接收通道故障事件。不幸的是,如果 tcp 连接到用作中继的机器(使用 SSH 的全局绑定远程转发),则操作系统不会看到 TCP 连接关闭(因为与中继机器的连接实际上并未关闭)。这导致了我在测试中观察到的奇怪行为。【参考方案2】:

试试这个检查回调对象是否仍然有效:

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

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

【讨论】:

不推荐使用此解决方案,因为回调通道可能在您检查状态和对通道执行任何操作之间发生故障。 是的,您仍然应该处理任何异常,因为没有真正的方法可以确定。

以上是关于在 WCF 双工合同中检测客户端死亡的主要内容,如果未能解决你的问题,请参考以下文章

在双工绑定 WCF 应用程序中处理丢弃的客户端

WCF 双工服务和 TCP 端口耗尽

WCF 双工合同

在双工 WCF 服务的客户端中使用基于任务的异步模式时出错

请求-响应与双工 WCF 消息交换模式

影响客户端的 WCF 合同更改