WCF,BasicHttpBinding:停止新连接但允许现有连接继续
.NET 3.5,VS2008,使用BasicHttpBinding的WCF服务
我有一个Windows服务托管的WCF服务。 当Windows服务因升级,定期维护等原因关闭时,我需要正常关闭我的WCF服务。 WCF服务的方法可能需要几秒钟才能完成,而典型的卷每秒需要2-5个方法调用。 我需要以允许任何先前调用方法完成的方式关闭WCF服务,同时拒绝任何新调用。 通过这种方式,我可以在约5-10秒内达到安静状态,然后完成Windows服务的关闭周期。
调用ServiceHost.Close似乎是正确的方法,但它会立即关闭客户端连接,而无需等待任何正在进行的方法完成。 我的WCF服务完成了它的方法,但是没有人发送响应,因为客户端已经断开连接。 这是此问题提出的解决方案。
以下是事件序列:
- 客户端使用VS生成的代理类调用服务方法
- 服务开始执行服务方法
- 服务收到关闭请求
- 服务调用ServiceHost.Close(或BeginClose)
- 客户端已断开连接,并收到System.ServiceModel.CommunicationException
- 服务完成服务方法。
- 最终服务检测到它没有更多的工作要做(通过应用程序逻辑)并终止。
我需要的是客户端连接保持打开,以便客户知道他们的服务方法成功完成。 现在他们只是获得了一个封闭的连接,并且不知道服务方法是否成功完成。 在使用WCF之前,我使用套接字并且能够通过直接控制Socket来实现。 (即在仍然执行接收和发送时停止接受循环)
关闭主机HTTP端口非常重要,以便上游防火墙可以将流量定向到另一个主机系统,但现有连接保持打开状态以允许现有方法调用完成。
有没有办法在WCF中实现这一目标?
我尝试过的事情:
- ServiceHost.Close() – 立即关闭客户端
- ServiceHost.ChannelDispatchers – 在每个上调用Listener.Close() – 似乎什么都不做
- ServiceHost.ChannelDispatchers – 在每个上调用CloseInput() – 立即关闭客户端
- 覆盖ServiceHost.OnClosing() – 让我延迟关闭,直到我决定关闭,但在此期间允许新的连接
- 使用此处描述的技术删除端点。 这抹去了一切。
- 运行网络嗅探器以观察ServiceHost.Close()。 主机只关闭连接,不发送任何响应。
谢谢
编辑 :不幸的是,我无法实现系统正在关闭的应用程序级别的咨询响应,因为该领域的客户端已经部署。 (我只控制服务,而不是客户端)
编辑 :我使用Redgate Reflector来查看Microsoft的ServiceHost.Close实现。 不幸的是,它调用了一些我的代码无法访问的internal
帮助器类。
编辑 :我还没有找到我想要的完整解决方案,但是Benjamin建议在输入服务方法之前使用IMessageDispatchInspector拒绝请求。
猜测:
您是否尝试在运行时(从端点)获取绑定,将其转换为BasicHttpBinding并(重新)定义其中的属性?
我最好的猜测:
- OpenTimeout
- MaxReceivedMessageSize
- ReaderQuotas
这些可以根据文档在运行时设置,并且似乎允许所需的行为(阻止新客户端)。 这对“上游防火墙/负载均衡器需要重新路由”部分无济于事。
最后一个猜测:你可以(文件说是,但我不确定后果是什么)根据需要将端点的地址重新定义为本地主机地址? 这可能作为防火墙主机的“端口关闭”,如果它不会杀死所有客户端..
编辑:在玩上面的建议和有限的测试时,我开始玩消息检查器/行为组合,现在看起来很有希望:
public class WCFFilter : IServiceBehavior, IDispatchMessageInspector { private readonly object blockLock = new object(); private bool blockCalls = false; public bool BlockRequests { get { lock (blockLock) { return blockCalls; } } set { lock (blockLock) { blockCalls = !blockCalls; } } } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this); } } } public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { lock (blockLock) { if (blockCalls) request.Close(); } return null; } public void BeforeSendReply(ref Message reply, object correlationState) { } }
忘记糟糕的锁用法等,但是使用这个非常简单的WCF测试(返回一个带有Thread.Sleep的随机数),如下所示:
var sh = new ServiceHost(new WCFTestService(), baseAdresses); var filter = new WCFFilter(); sh.Description.Behaviors.Add(filter);
然后翻转BlockRequests属性我得到以下行为(再次:这当然是一个非常非常简单的例子,但我希望它可能对你有用):
//我产生3个线程请求一个数字..
请求号码..
请求号码..
//一个传入请求的服务器端日志
传入的号码请求。
//主循环翻转“阻止一切”布尔
从此处阻止访问。
//之后再增加3个客户,以获得良好的衡量标准
请求号码..
请求号码..
请求号码..
//第一个请求(使用服务器端日志,见上文)成功完成
收到1569129641
//所有其他消息从未进入服务器并且因故障而死亡
块后生成的客户端请求出错。
块后生成的客户端请求出错。
块后生成的客户端请求出错。
块之前的客户端请求出错。
块之前的客户端请求出错。
上游防火墙有api吗? 我们在应用程序中执行此操作的方式是停止在负载均衡器级别进入的新请求,然后当所有请求都已完成处理时,我们可以重新启动服务器和服务。
我的建议是在服务进入“停止状态”时设置EventHandler,使用OnStop方法。 设置EventHandler,指示您的服务进入停止状态。
您的正常服务循环应该检查是否设置了此事件,如果是,则向调用客户端返回“服务正在停止消息”,并且不允许它进入正常例程。
在OnStop方法继续运行以杀死WCF主机(ServiceHost.Close)之前,仍然有活动进程在运行时完成。
另一种方法是通过实现自己的引用计数器来跟踪活动的调用。 然后,您将知道何时可以在参考计数器达到零时停止服务主机,并通过执行上述检查来确定何时启动停止事件。
希望这可以帮助。
我自己没有实现这个,所以YMMV,但我相信你要做的是在完全停止服务之前暂停服务。 暂停可用于在完成现有请求时拒绝新连接。
在.NET中,暂停服务的方法似乎是使用ServiceController 。
此WCF服务是否以任何方式validation用户? 你有“握手”方法吗?
我认为您可能需要编写自己的实现,其中包含一个跟踪所有正在运行的请求的帮助程序类,然后在请求关闭时,您可以查看是否有任何内容仍在运行,基于此延迟关闭…(使用计时器可能?)
不确定阻止进一步的传入请求…你应该有一个全局变量告诉你的应用程序是否请求关闭,所以你可以拒绝进一步的请求……
希望这可以帮到你。
也许你应该设置
ServiceBehaviorAttribute和OperationBehavior属性。 在MSDN上查看此信息
除了Matthew Steeples的回答。
像F5等最严重的负载平衡器具有识别节点是否存活的机制。 在您的情况下,似乎检查某个端口是否打开。 但是可以轻松配置替代方法。
因此,您可以公开两个服务:提供请求的真实服务,以及监控“心跳”式服务。 转换到维护模式时,您可以先使监控服务脱机,这将使负载远离节点,并且仅在所有请求完成处理后关闭实际服务。 听起来有点奇怪,但在你的场景中可能会有所帮助……