WCF 代理管理器 - 无配置






4.80/5 (8投票s)
让 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;
}