问题 WCF ChannelFactory和频道 - 缓存,重用,关闭和恢复


我的WCF客户端库有以下规划的体系结构:

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

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

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

11661
2018-01-27 11:49


起源

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


答案:


我想迟到然后永远不会......看起来像作者有它工作,这可能会帮助未来的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)创建公共代理基类并从中派生所有客户端。这可能很有用,比如重新连接,使用预调用/后调用逻辑,从工厂消耗事件(例如,Faulted,Opening)

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

12
2018-03-02 05:32



@TCC你尝试过另一种选择吗?你用过吗? 良好的模式和做法 ? - Kiquenet
适用于 ServiceClient ?喜欢 MyUserServiceClient : System.ServiceModel.ClientBase<Portal.Admin.MobileServicesUsuario.IUsuarioService>, Portal.Admin.MobileServicesUsuario.IUsuarioService - Kiquenet
任何代码示例使用 良好的模式和做法 对于 CustomChannelFactory 和 common proxy base class, re-connect logic ,和 ProxyBase class? - Kiquenet