65.9K
CodeProject 正在变化。 阅读更多。
Home

WCF 代理管理器 - 无配置

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (8投票s)

2012 年 4 月 9 日

CPOL

5分钟阅读

viewsIcon

101680

downloadIcon

627

让 WCF 配置变得轻量、对开发者友好且持久……是的,无需 ping 即可持久。

引言

本文演示了一种创建持久化 `IClientChannel` 的模式,该模式不需要大量配置即可执行。这简化了部署,提高了可靠性,并且说实话,使一线开发者的代码易于开发。

背景

WCF 彻底改变了 .NET 层的通信方式。虽然它在可扩展性方面具有革命性,但也让许多开发者怀念起 Web 服务的美好时光。WCF 的一个主要缺点也是它最大的优点。它确实是一个非常可配置的通信工具。然而,在部署和开发代码时,这种配置往往成为最大的挑战。此外,`IClientChannel` 本身也很脆弱。一旦创建了一个通道,很容易就会中断或进入故障状态。

使用代码

图表

实现

在本例中,我创建了两个简单的 WCF 服务,默认绑定为 HTTP,端口为 8080。需要注意的是,如果您打算使用 TCP 和自托管 WCF 服务来使用此模式,您将不得不使用 .NET 端口共享服务。然后,我使用 svcutil 生成了两个代理文件。接着创建一个名为 `Proxies` 的新公共类。

namespace HelloWorldServiceProxies
{
    public class Proxies
    {
        /// <summary>
        /// </summary>
        public static IHelloWorld HelloWorldService
        {
            get { return ProxyManager.GetProxy<IHelloWorld>(); }
        }

        /// <summary>
        /// </summary>
        public static IGoodByeWorld GoodByeWorldService
        {
            get { return ProxyManager.GetProxy<IGoodByeWorld>(); }
        }
    }
}

如您所见,有两个静态属性代表了我想要访问的代理。以下是调用代码的简单示例

var helloReturn = Proxies.HelloWorldService.Hello();

根本不需要将代码包装在 `using` 块中。您可能会问为什么?让我们看看实际上发生了什么?首先,我们将查看被调用的 `GetProxy` 方法的内部。

// Return an Encapsulated IClientChannel
public static T GetProxy<T>()
{
    var t = typeof (T);
    if (!_proxyCache.ContainsKey(t))
    {
        try
        {
            _proxyCache.Add(t, createProxy<T>());
        }
        catch (Exception ex)
        {
            throw new Exception("Failed to create provider: " + ex.Message, ex);
        }
    }
    var s = (ProxyBase<T>) _proxyCache[t];
    var ic = (IClientChannel) s.InnerChannel;
    //here is the key Abort anything and force dispose
    ic.Abort();
    // Recreate the channel there is a small amount of overhead here about 4-5 milliseconds 
    // Well worth the ease of deployment / durability
    s.SetChannel();
    return s.InnerChannel;
} 

这非常简单,我们通过对类型参数执行 `typeof` 来创建一个键。然后,我们检查代理包装器是否已缓存。如果没有,我们则创建它。一旦我们有了它,我们就将其缓存起来,并在重置其底层连接后返回其内部通道(客户端通道)。这一点非常重要,因为它使缓存的代理持久化。如果您在调用 1 和调用 2 之间出现网络中断,使用此方法可以确保不会有问题。

我曾尝试在每个服务上添加一个 `ping()` 方法并调用它,但发现没有简单的方法可以做到这一点,并且如果 ping 失败,我们将不得不这样做。无论如何,我都需要重置通道。考虑到重置通道只需要 4-5 毫秒(大约与返回 ping 的时间相同),为什么不确保我们获得一个干净的通道呢?

现在,让我们更深入地了解一下通道工厂(Channel Factories)这个令人畏惧的世界,或者我称之为 WCF 的巫毒科学。让我们看看 `CreateProxy` 代码。

/// <summary>
///   This is where we new up an encapsulated IClientChannel
/// </summary>
/// <typeparam name="T"> </typeparam>
/// <returns> </returns>
internal static ProxyBase<T> createProxy<T>()
{
    var t = typeof (T);
    // Get The Channel Factory or create it if needed
    ChannelFactory channelFactory;
    lock (_channelFactoryCache)
    {
        if (_channelFactoryCache.ContainsKey(t))
        {
            channelFactory = (ChannelFactory) _channelFactoryCache[t];
        }
        else
        {
            channelFactory = createChannelFactory<T>();
            _channelFactoryCache.Add(t, channelFactory);
        }
    }
    EndpointAddress endpoint = null;
    //get Configuration
    var s = ConfigurationHelper.GetKey("HOST", Environment.MachineName);
    var port = ConfigurationHelper.GetKey("PORT", "8080");
    var binding = ConfigurationHelper.GetKey("BINDING", "HTTP");
    var serviceName = typeof (T).ToString();
    //Ser the correct service name Defaults to the interface name minus the I
    if (serviceName[0] == char.Parse("I"))
    {
        serviceName = serviceName.Remove(0, 1);
    }
    //Create the URI
    string server;
    switch (binding)
    {
        case "TCP":
            server = string.Format("net.tcp://" + getIPAddress(s) + ":{0}/{1}", port, serviceName);
            endpoint = new EndpointAddress(server);
            break;
        case "HTTP":
            server = string.Format("http://" + getIPAddress(s) + ":{0}/{1}", port, serviceName);
            endpoint = new EndpointAddress(server);
            break;
    }
    //Create the Enapsulated IClientChanenel
    var pb = new ProxyBase<T>((ChannelFactory<T>) channelFactory, endpoint);
    return pb;
}

`CreateProxy` 方法本身非常简单。如您所见,我们首先创建一个键,然后获取缓存的通道工厂或在需要时创建一个。

/// <summary>
/// </summary>
/// <typeparam name="> </typeparam />
/// <returns /> </returns />
/// <exception cref="ArgumentException" />
private static ChannelFactory createChannelFactory<t>()
{
    Binding b = null;
    switch (ConfigurationHelper.GetKey("BINDING", "HTTP"))
    {
        case "HTTP":
            b = new BasicHttpBinding();

            break;
        case "TCP":
            b = new NetTcpBinding();
            break;
    }

    if (b != null)
    {
        var factory = new ChannelFactory<t>(b);
        // This is super important
        // Why ? This is where you can add
        // Custom behaviors to your outgoing calls
        // like Message Inspectors ... or behaviors.
        ApplyContextToChannelFactory(factory);

        return factory;
    }
    return null;
}

下一部分是一些简单的配置选项示例。HOST、PORT 和 BINDING 是我创建的三个简单的可配置键。这些键用于构建终结点并创建正确的通道工厂。

//the correct service name Defaults to the interface name minus the I
if (serviceName[0] == char.Parse("I"))
{
     serviceName = serviceName.Remove(0, 1);
}

如上所示,通过使用接口名称减去“I”,我们应该能够可靠地为特定服务创建默认 URI。一旦我们有了终结点,我们就将通道工厂和终结点传递给代理包装器。该类将使用通道工厂和终结点来设置通道。

性能

参数:两个服务调用,一个调用 HelloService,一个调用 GoodbyeService。每个调用都执行 1000 次。

数字

  • 传统方法:平均每次 2 次调用耗时 4.1 毫秒,1000 次迭代总计 6748 毫秒。
  • 代理管理器:平均每次 2 次调用耗时 9.1 毫秒,1000 次运行总计 9551 毫秒。

让我们来看看这些数字。不要让任何好的代码未经测试。当我们运行一些性能指标时,我们发现创建默认 WCF 通道并进行简单调用的平均时间大约为 4 毫秒。`Proxymanager` 代码带来了一点性能损耗。它在类似调用上的平均时间约为 9 毫秒。

听起来很糟糕?各位,我们讨论的是毫秒。在附带的源代码中,我有两个测试工具项目。一个重要说明:一个是使用传统方法,另一个是使用我的代理管理器方法。请注意,在我新的方法中,您不必担心处置通道,因为每次使用时都会重新创建它们。这极大地提高了您的编码便捷性。

关注点

我不知道您怎么想,但三个简单的 appsettings 要管理起来容易得多。我敢肯定,有些人会问,为什么要费力去做?好吧,当您只有一两个地方可以部署代码并且您负责部署时,我能理解您的观点。在一个大型企业中,我有很多测试/u.a.t/srt 等环境,配置确实变成了一种负担。

现在,您可以想象,这只有在您不尝试手动覆盖服务名称时才真正有效。在另一篇文章中,我将讨论如何以编程方式托管您的合同,甚至为消息添加压缩。

后续问题

关于 ChannelFactory 的创建,有一些问题。这个问题提得很好:“为什么需要它”。我包含了一个原因的例子。有时您需要特定的绑定选项。通常这可以通过配置文件选项来处理。在我们的场景中,没有硬编码是不可能的。在以下代码示例中,我就是这样做的,以演示可以在通道工厂上设置哪些类型的选项。

protected override ChannelFactory CreateChannelFactory<I, T>(T context)
{
    if (object.Equals(context, default(T)))
    {
        throw new ArgumentException("The argument 'context' cannot be null.");
    }
    ChannelFactory<I> factory = null;
    var t = new NetTcpBinding
    {
        CloseTimeout = new TimeSpan(0, 0, 10, 0),
        OpenTimeout = new TimeSpan(0, 0, 10, 0),
        ReceiveTimeout = new TimeSpan(0, 1, 0, 0),
        SendTimeout = new TimeSpan(0, 0, 10, 0),
        TransactionFlow = false,
        TransferMode = TransferMode.Buffered,
        TransactionProtocol = TransactionProtocol.OleTransactions,
        HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
        ListenBacklog = 500,
        MaxBufferPoolSize = 524288,
        MaxBufferSize = 2147483647,
        MaxConnections = 2000,
        MaxReceivedMessageSize = 2147483647,
        ReaderQuotas =
        {
            MaxDepth = 128,
            MaxStringContentLength = int.MaxValue,
            MaxArrayLength = 2147483647,
            MaxBytesPerRead = 16384,
            MaxNameTableCharCount = 16384
        }
    };
    t.ReliableSession.Ordered = true;
    t.ReliableSession.InactivityTimeout = new TimeSpan(0, 0, 10, 0);
    t.ReliableSession.Enabled = false;
    factory = new ChannelFactory<I>(t);
    ApplyContextToChannelFactory<T>(context, factory);
    return factory;
}
© . All rights reserved.