WCF ChannelFactory 和通道 - 缓存、重用、关闭和恢复

Posted

技术标签:

【中文标题】WCF ChannelFactory 和通道 - 缓存、重用、关闭和恢复【英文标题】:WCF ChannelFactory and channels - caching, reusing, closing and recovery 【发布时间】:2013-01-10 21:44:41 【问题描述】:

我为我的 WCF 客户端库计划了以下架构:

使用 ChannelFactory 而不是 svcutil 生成代理,因为 我需要更多的控制权,而且我想把客户分开 组装并避免在我的 WCF 服务更改时重新生成 需要将带有消息检查器的行为应用到我的 WCF 端点,因此每个通道都能够发送其 自己的身份验证令牌 我的客户端库将从 MVC 前端使用,因此我必须考虑可能的线程问题 我正在使用 .NET 4.5(也许它有一些帮助程序或新方法可以更好地实现 WCF 客户端?)

我已经阅读了很多关于各种不同部分的文章,但我仍然对如何以正确的方式将它们组合在一起感到困惑。我有以下问题:

    据我了解,建议将 ChannelFactory 缓存在静态变量中,然后从中取出通道,对吗? 是特定于整个 ChannelFactory 的端点行为还是我可以分别为每个通道应用我的身份验证行为?如果行为特定于整个工厂,这意味着我不能在端点行为对象中保留任何状态信息,因为相同的身份验证令牌将被每个通道重用,但显然我希望每个通道都有自己的身份验证令牌当前用户。这意味着,我必须在端点行为中计算令牌(我可以将它保存在 HttpContext 中,我的消息检查器行为只会将它添加到传出消息中)。 我的客户端类是一次性的(实现 IDispose)。我如何正确处置通道,知道它可能处于任何可能的状态(未打开、打开、失败......)?我只是丢弃它吗?我要中止它然后丢弃吗?我要关闭它(但它可能根本没有打开)然后丢弃吗? 如果我在使用频道时遇到问题怎么办?是只有通道坏了还是整个 ChannelFactory 坏了?

我猜,一行代码说一千多个单词,所以这里是我的代码形式的想法。我已经用“???”标记了我上面的所有问题在代码中。

public class MyServiceClient : IDisposable

    // channel factory cache
    private static ChannelFactory<IMyService> _factory;
    private static object _lock = new object();

    private IMyService _client = null;
    private bool _isDisposed = false;

     /// <summary>
    /// Creates a channel for the service
    /// </summary>
    public MyServiceClient()
    
        lock (_lock)
        
            if (_factory == null)
            
                // ... set up custom bindings here and get some config values

                var endpoint = new EndpointAddress(myServiceUrl);
                _factory = new ChannelFactory<IMyService>(binding, endpoint);

                // ???? do I add my auth behavior for entire ChannelFactory 
                // or I can apply it for individual channels when I create them?
            
        

        _client = _factory.CreateChannel();
    

    public string MyMethod()
    
        RequireClientInWorkingState();
        try
        
            return _client.MyMethod();
        
        catch
        
            RecoverFromChannelFailure();
            throw;
        
    

    private void RequireClientInWorkingState()
    
        if (_isDisposed)
            throw new InvalidOperationException("This client was disposed. Create a new one.");

        // ??? is it enough to check for CommunicationState.Opened && Created?
        if (state != CommunicationState.Created && state != CommunicationState.Opened)
            throw new InvalidOperationException("The client channel is not ready to work. Create a new one.");
    

    private void RecoverFromChannelFailure()
    
        // ??? is it the best way to check if there was a problem with the channel?
        if (((IChannel)_client).State != CommunicationState.Opened)
        
            // ??? is it safe to call Abort? won't it throw?
            ((IChannel)_client).Abort();
        

        // ??? and what about ChannelFactory? 
        // will it still be able to create channels or it also might be broken and must be thrown away? 
        // In that case, how do I clean up ChannelFactory correctly before creating a new one?
    

    #region IDisposable

    public void Dispose()
        
        // ??? is it how to free the channel correctly?
        // I've heard, broken channels might throw when closing 
        // ??? what if it is not opened yet?
        // ??? what if it is in fault state?
        try
        
            ((IChannel)_client).Close();
        
        catch
        
           ((IChannel)_client).Abort();              
        

        ((IDisposable)_client).Dispose();

        _client = null;
        _isDisposed = true;
    

    #endregion

【问题讨论】:

我最终实现了几乎与上述类似的实现,而且它似乎工作正常。我在 RecoverFromChannelFailure 中添加了一些代码来处理损坏的工厂:lock (_lock) if (_factory.State != CommunicationState.Opened) _factory.Abort();_factory = null;;而且我还有一个 Initialize 方法,它检查工厂是否已经消失,然后创建一个新的。 关于身份验证,我最终得到了一个自定义 MessageInterceptorBehavior : IEndpointBehavior, IClientMessageInspector, IDispatchMessageInspector,它具有被 WCF 为服务器和客户端调用的 AfterReceiveRequest 方法。 感谢您的更新!处理破碎的工厂是一个我可以忘记的案例。顺便说一句,我在重用客户端通道时遇到了一些问题:在跟踪中看到频繁但看起来随机的 TCP 995 异常;这就是我问的原因。最后,重用工厂但每次都重新创建客户渠道为我解决了这个问题。由于下面的 TCP 连接是池化的,它似乎没有太大的成本,虽然我没有测量。 【参考方案1】:

我想迟到总比永不好......而且看起来作者已经工作了,这可能会对未来的 WCF 用户有所帮助。

1) ChannelFactory 安排包含通道所有行为的通道。通过 CreateChannel 方法创建通道会“激活”通道。通道工厂可以被缓存。

2) 你用绑定和行为来塑造通道工厂。此形状与创建此频道的每个人共享。正如您在评论中指出的那样,您可以附加消息检查器,但更常见的情况是使用 Header 将自定义状态信息发送到服务。您可以通过 OperationContext.Current 附加标头

using (var op = new OperationContextScope((IContextChannel)proxy))

    var header = new MessageHeader<string>("Some State");
    var hout = header.GetUntypedHeader("message", "urn:someNamespace");
    OperationContext.Current.OutgoingMessageHeaders.Add(hout);

3) 这是我处理客户端通道和工厂的一般方法(此方法是我的 ProxyBase 类的一部分)

public virtual void Dispose()

    CloseChannel();
    CloseFactory();


protected void CloseChannel()

    if (((IChannel)_client).State == CommunicationState.Opened)
    
        try
        
            ((IChannel)_client).Close();
        
        catch (TimeoutException /* timeout */)
        
            // Handle the timeout exception
            ((IChannel)innerChannel).Abort();
        
        catch (CommunicationException /* communicationException */)
        
            // Handle the communication exception
            ((IChannel)_client).Abort();
        
    


protected void CloseFactory()

    if (Factory.State == CommunicationState.Opened)
    
        try
        
            Factory.Close();
        
        catch (TimeoutException /* timeout */)
        
            // Handle the timeout exception
            Factory.Abort();
        
        catch (CommunicationException /* communicationException */)
        
            // Handle the communication exception
            Factory.Abort();
        
    

4) WCF 将故障通道而不是工厂。您可以实现重新连接逻辑,但这需要您从某些自定义 ProxyBase 中创建和派生客户端,例如

protected I Channel

    get
    
        lock (_channelLock)
        
            if (! object.Equals(innerChannel, default(I)))
            
                ICommunicationObject channelObject = innerChannel as ICommunicationObject;
                if ((channelObject.State == CommunicationState.Faulted) || (channelObject.State == CommunicationState.Closed))
                
                    // Channel is faulted or closing for some reason, attempt to recreate channel
                    innerChannel = default(I);
                
            

            if (object.Equals(innerChannel, default(I)))
            
                Debug.Assert(Factory != null);
                innerChannel = Factory.CreateChannel();
                ((ICommunicationObject)innerChannel).Faulted += new EventHandler(Channel_Faulted);
            
        

        return innerChannel;
    

5) 不要重复使用频道。打开,做某事,关闭是正常的使用模式。

6) 创建通用代理基类并从中派生所有客户端。这可能很有帮助,例如重新连接、使用调用前/调用后逻辑、使用工厂中的事件(例如故障、打开)

7) 创建您自己的 CustomChannelFactory 这使您可以进一步控制工厂的行为方式,例如设置默认超时,强制执行各种绑定设置(MaxMessageSizes)等。

public static void SetTimeouts(Binding binding, TimeSpan? timeout = null, TimeSpan? debugTimeout = null)
        
            if (timeout == null)
            
                timeout = new TimeSpan(0, 0, 1, 0);
            
            if (debugTimeout == null)
            
                debugTimeout = new TimeSpan(0, 0, 10, 0);
            
            if (Debugger.IsAttached)
            
                binding.ReceiveTimeout = debugTimeout.Value;
                binding.SendTimeout = debugTimeout.Value;
            
            else
            
                binding.ReceiveTimeout = timeout.Value;
                binding.SendTimeout = timeout.Value;
            
        

【讨论】:

@TCC 您是否尝试过其他选择?您是否使用了良好的模式和实践 适用于 ServiceClient ?喜欢MyUserServiceClient : System.ServiceModel.ClientBase&lt;Portal.Admin.MobileServicesUsuario.IUsuarioservice&gt;, Portal.Admin.MobileServicesUsuario.IUsuarioService 对于CustomChannelFactorycommon proxy base classre-connect logicProxyBase class 使用良好模式和实践 的任何代码示例?跨度> 很好的答案!我知道这是一个旧帖子,但也许您仍然可以回答:ChannelFactory 是否应该与 Channel 同时被处置?您提到缓存工厂(以便在代理之间重用),那么最好有一些逻辑允许仅在没有更多通道处于活动状态时处理工厂?

以上是关于WCF ChannelFactory 和通道 - 缓存、重用、关闭和恢复的主要内容,如果未能解决你的问题,请参考以下文章

为啥缓存 WCF 通道是一件坏事?

ChannelFactory 会出现故障吗?

WCF如何创建连接到同一个服务实例的两个通道

WCF ChannelFactory 和连接超时的最佳实践

ChannelFactory:Channel Close() 未关闭连接(netstat 显示 ESTABLISHED)

WCF,ChannelFactory,“找不到端点元素...”