WCF,BasicHttpBinding:停止新连接但允许现有连接继续

时间:2009-12-17 15:33:34

标签: c# wcf basichttpbinding communicationexception

.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. 最终服务检测到它没有更多工作要做(通过应用程序逻辑)并终止。
  8. 我需要的是让客户端连接保持打开状态,以便客户知道他们的服务方法已经成功完成。现在他们只是获得了一个封闭的连接,并且不知道服务方法是否成功完成。在使用WCF之前,我使用套接字并且能够通过直接控制Socket来实现。 (即在仍然执行接收和发送时停止接受循环)

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

    有没有办法在WCF中完成此任务?

    我尝试过的事情:

    1. ServiceHost.Close() - 立即关闭客户
    2. ServiceHost.ChannelDispatchers - 在每个上调用Listener.Close() - 似乎什么都不做
    3. ServiceHost.ChannelDispatchers - 在每个上调用CloseInput() - 立即关闭客户端
    4. 覆盖ServiceHost.OnClosing() - 让我延迟关闭直到我决定关闭,但在此期间允许新连接
    5. 使用technique described here删除端点。这抹去了一切。
    6. 运行网络嗅探器以观察ServiceHost.Close()。主机只关闭连接,不发送任何响应。
    7. 由于

      编辑:遗憾的是,我无法实施系统正在关闭的应用程序级别的咨询响应,因为该字段中的客户端已经部署完毕。 (我只控制服务,而不是客户)

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

      编辑:我还没有找到我想要的完整解决方案,但Benjamin建议在输入服务方法之前使用IMessageDispatchInspector拒绝请求。

8 个答案:

答案 0 :(得分:6)

猜测:

您是否尝试在运行时(从端点)获取绑定,将其强制转换为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<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个客户,为了更好的衡量 请求号码。
请求号码。
请求号码。
//第一个请求(使用服务器端日志,见上文)成功完成 收到1569129641
//所有其​​他消息从未进入服务器并且因故障而死亡 块后产生的客户端请求出错 块后产生的客户端请求出错 块后产生的客户端请求出错 块之前的客户端请求出错 在阻止之前客户端请求出错。

答案 1 :(得分:2)

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

答案 2 :(得分:1)

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

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

虽然您仍然有活动的进程在运行,但是在OnStop方法继续杀死WCF主机(ServiceHost.Close)之前完成它。

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

希望这有帮助。

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

此WCF服务是否以任何方式验证用户? 你有“握手”方法吗?

答案 5 :(得分:0)

我认为您可能需要使用帮助程序类来编写自己的实现,该辅助类跟踪所有正在运行的请求,然后在请求关闭时,您可以查看是否仍有任何内容正在运行,基于此延迟关闭... (可能使用计时器?)

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

希望这可以帮助你。

答案 6 :(得分:0)

也许你应该设置

ServiceBehaviorAttribute和OperationBehavior属性。在MSDN

上查看此内容

答案 7 :(得分:0)

除了Matthew Steeples的回答。

像F5等最重要的负载平衡器具有识别节点是否存活的机制。在您的情况下,似乎检查某个端口是否打开。但可以轻松配置其他方式。

所以你可以揭露,例如两项服务:提供请求的真实服务,以及监控“心跳”式服务。转换到维护模式时,您可以先使监控服务脱机,这将使负载远离节点,并且仅在所有请求完成处理后关闭实际服务。听起来有点奇怪,但在你的场景中可能会有所帮助...