11

.NET 3.5、VS2008、使用 BasicHttpBinding 的 WCF 服务

我有一个托管在 Windows 服务中的 WCF 服务。当 Windows 服务因升级、定期维护等原因关闭时,我需要优雅地关闭我的 WCF 服务。WCF 服务的方法最多可能需要几秒钟才能完成,典型的量是每秒 2-5 次方法调用。我需要以允许任何先前调用方法完成的方式关闭 WCF 服务,同时拒绝任何新调用。通过这种方式,我可以在约 5-10 秒内达到安静状态,然后完成我的 Windows 服务的关闭周期。

调用ServiceHost.Close似乎是正确的方法,但它会立即关闭客户端连接,而无需等待任何正在进行的方法完成。我的 WCF 服务完成了它的方法,但是没有人可以发送响应,因为客户端已经断开连接。这是this question建议的解决方案。

以下是事件的顺序:

  1. 客户端调用服务上的方法,使用 VS 生成的代理类
  2. 服务开始执行服务方法
  3. 服务收到关闭请求
  4. 服务调用 ServiceHost.Close(或 BeginClose)
  5. 客户端断开连接,并收到 System.ServiceModel.CommunicationException
  6. 服务完成服务方法。
  7. 最终服务检测到它没有更多工作要做(通过应用程序逻辑)并终止。

我需要的是让客户端连接保持打开状态,以便客户端知道他们的服务方法已成功完成。现在他们只是得到一个关闭的连接,不知道服务方法是否成功完成。在使用 WCF 之前,我使用的是套接字,并且能够通过直接控制套接字来做到这一点。(即在仍在进行接收和发送的同时停止接受循环)

关闭主机 HTTP 端口很重要,这样上游防火墙可以将流量引导到另一个主机系统,但现有连接保持打开状态以允许完成现有方法调用。

有没有办法在 WCF 中实现这一点?

我尝试过的事情:

  1. ServiceHost.Close() - 立即关闭客户端
  2. ServiceHost.ChannelDispatchers - 在每个上调用 Listener.Close() - 似乎没有做任何事情
  3. ServiceHost.ChannelDispatchers - 在每个上调用 CloseInput() - 立即关闭客户端
  4. Override ServiceHost.OnClosing() - 让我延迟关闭,直到我决定可以关闭,但在此期间允许新连接
  5. 使用此处描述的技术删除端点。这抹杀了一切。
  6. 运行网络嗅探器以观察 ServiceHost.Close()。主机只是关闭连接,没有响应发送。

谢谢

编辑:不幸的是,我无法实现系统正在关闭的应用程序级咨询响应,因为现场的客户端已经部署。(我只控制服务,不控制客户端)

编辑:我使用 Redgate Reflector 来查看 Microsoft 的 ServiceHost.Close 实现。不幸的是,它调用了一些internal我的代码无法访问的辅助类。

编辑:我还没有找到我正在寻找的完整解决方案,但本杰明建议使用 IMessageDispatchInspector 在进入服务方法之前拒绝请求最接近。

4

8 回答 8

6

猜测:

您是否尝试在运行时(从端点)获取绑定,将其转换为 BasicHttpBinding 并(重新)定义那里的属性?

我的最佳猜测:

  • 打开超时
  • MaxReceivedMessageSize
  • 读者配额

这些可以根据文档在运行时设置,并且似乎允许所需的行为(阻止新客户端)。不过,这对“上游防火墙/负载均衡器需要重新路由”部分没有帮助。

最后的猜测:你能(文档说是,但我不确定后果是什么)根据需要将端点的地址重新定义为本地主机地址吗?如果它无论如何都不会杀死所有客户端,这也可以作为防火墙主机的“端口关闭”。

编辑:在使用上述建议和有限测试时,我开始使用目前看起来很有希望的消息检查器/行为组合:

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<ServiceEndpoint> 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 个线程请求一个数字..
请求一个数字..
请求一个数字..
// 一个传入请求的服务器端日志
传入一个数字的请求。
// 主循环翻转“阻止一切” bool
从这里开始阻止访问。
// 之后又多了 3 个客户端,这是很好的措施
请求一个号码..
请求一个号码..
请求一个号码..
// 第一个请求(带有服务器端日志,见上文)成功完成
Received 1569129641
// All other messages never made它还没有发送到服务器,并且
在块之后产生的客户端请求中出现错误错误。
块后产生的客户端请求错误。
块后产生的客户端请求错误。
块前客户端请求出错。
块前客户端请求出错。

于 2009-12-21T14:21:20.550 回答
2

上游防火墙有api吗?我们在应用程序中执行此操作的方式是在负载均衡器级别停止新请求,然后当所有请求完成处理后,我们可以重新启动服务器和服务。

于 2009-12-21T13:12:31.993 回答
1

我的建议是在您的服务进入“停止状态”时设置一个 EventHandler,使用 OnStop 方法。设置 EventHandler 指示您的服务将进入停止状态。

您的正常服务循环应该检查是否设置了此事件,如果设置了,则向调用客户端返回“服务正在停止消息”,并且不允许它进入您的正常例程。

当您仍然有活动进程在运行时,让它完成,然后 OnStop 方法继续终止 WCF 主机 (ServiceHost.Close)。

另一种方法是通过实现您自己的引用计数器来跟踪活动调用。然后,您将知道何时可以停止服务主机,一旦引用计数器达到零,并通过执行上述检查何时启动停止事件。

希望这可以帮助。

于 2009-12-21T14:18:35.113 回答
0

我自己没有实现这个,所以 YMMV,但我相信你想要做的是在完全停止服务之前暂停服务。暂停可用于在完成现有请求时拒绝新连接。

在 .NET 中,暂停服务的方法似乎是使用ServiceController

于 2009-12-17T15:38:29.880 回答
0

此 WCF 服务是否以任何方式对用户进行身份验证?你有什么“握手”的方法吗?

于 2009-12-21T12:57:45.093 回答
0

我认为您可能需要使用跟踪所有正在运行的请求的帮助程序类编写自己的实现,然后当请求关闭时,您可以找出是否有任何东西仍在运行,基于此延迟关闭......(使用也许是计时器?)

不确定是否阻止进一步传入的请求...您应该有一个全局变量来告诉您的应用程序是否请求关闭,因此您可以拒绝进一步的请求...

希望这可以帮助你。

于 2009-12-21T13:21:18.173 回答
0

也许你应该设置

ServiceBehaviorAttribute 和 OperationBehavior 属性。在MSDN上检查这个

于 2009-12-21T22:23:13.523 回答
0

除了来自 Matthew Steeples 的回答。

最重要的负载均衡器(如 F5 等)具有识别节点是否处于活动状态的机制。在您的情况下,它似乎检查某个端口是否打开。但是可以轻松配置替代方法。

因此,您可以公开例如两个服务:为请求提供服务的真实服务,以及一个类似“心跳”的监控服务。过渡到维护模式时,您可以先使监控服务脱机,这将减轻节点的负载,并在所有请求处理完毕后才关闭真实服务。听起来有点奇怪,但可能对您的情况有所帮助...

于 2009-12-22T20:35:21.453 回答