WCF, BasicHttpBinding: остановка новых подключений, но продолжение существующих подключений - PullRequest
11 голосов
/ 17 декабря 2009

.NET 3.5, VS2008, служба WCF с использованием BasicHttpBinding

У меня есть служба WCF, размещенная в службе Windows. Когда служба Windows закрывается из-за обновлений, планового обслуживания и т. Д., Мне нужно изящно завершить свою службу WCF. Служба WCF имеет методы, выполнение которых может занять до нескольких секунд, а типичный объем составляет 2-5 вызовов метода в секунду. Мне нужно завершить службу WCF таким образом, чтобы можно было завершить любые ранее вызванные методы, при этом отказывая в новых вызовах. Таким образом, я могу достигнуть спокойного состояния через ~ 5-10 секунд, а затем завершить цикл выключения моей службы Windows.

Вызов ServiceHost.Close кажется правильным подходом, но он сразу закрывает клиентские соединения, не дожидаясь завершения каких-либо методов. Моя служба WCF завершает свой метод, но некому отправить ответ, потому что клиент уже был отключен. Это решение, предложенное этим вопросом .

Вот последовательность событий:

  1. Клиент вызывает метод в службе, используя сгенерированный VS прокси-класс
  2. Служба начинает выполнение метода службы
  3. Сервис получает запрос на отключение
  4. Служба вызывает ServiceHost.Close (или BeginClose)
  5. Клиент отключен и получает исключение System.ServiceModel.CommunicationException
  6. Сервис завершает метод обслуживания.
  7. В конце концов служба обнаруживает, что ей больше не нужно выполнять (через логику приложения), и завершает работу.

Мне нужно, чтобы клиентские соединения оставались открытыми, чтобы клиенты знали, что их методы обслуживания завершены успешно. Сейчас они просто получают закрытое соединение и не знают, успешно ли завершен метод обслуживания. До использования WCF я использовал сокеты и мог сделать это, управляя Socket напрямую. (то есть остановите цикл Accept, продолжая получать и отправлять)

Важно, чтобы HTTP-порт хоста был закрыт, чтобы вышестоящий брандмауэр мог направлять трафик на другую хост-систему, но существующие соединения оставались открытыми, чтобы позволить существующим вызовам методов завершиться.

Есть ли способ сделать это в WCF?

Вещи, которые я пробовал:

  1. ServiceHost.Close () - сразу закрывает клиентов
  2. ServiceHost.ChannelDispatchers - вызывать Listener.Close () для каждого - кажется, ничего не делает
  3. ServiceHost.ChannelDispatchers - вызывать CloseInput () для каждого - сразу же закрывать клиентов
  4. Переопределить ServiceHost.OnClosing () - позволяет мне отложить закрытие до тех пор, пока я не решу, что все в порядке, но в это время разрешены новые подключения
  5. Удалите конечную точку, используя метод , описанный здесь . Это уничтожает все.
  6. Запуск сетевого анализатора для наблюдения ServiceHost.Close (). Хост просто закрывает соединение, ответ не отправляется.

Спасибо

Редактировать : К сожалению, я не могу реализовать консультативный ответ на уровне приложения о том, что система закрывается, потому что клиенты на местах уже развернуты. (Я контролирую только службу, а не клиентов)

Редактировать : Я использовал отражатель Redgate, чтобы посмотреть на реализацию Microsoft ServiceHost.Close. К сожалению, он вызывает некоторые internal вспомогательные классы, к которым мой код не может получить доступ.

Редактировать : Я не нашел полного решения, которое искал, но предложение Бенджамина использовать IMessageDispatchInspector для отклонения запросов до входа в сервисный метод было ближе всего.

Ответы [ 8 ]

6 голосов
/ 21 декабря 2009

Гадание:

Вы пытались захватить привязку во время выполнения (от конечных точек), привести ее к BasicHttpBinding и (пере) определить свойства там?

Лучшие догадки от меня:

  • OpenTimeout
  • MaxReceivedMessageSize
  • ReaderQuotas

Они могут быть установлены во время выполнения в соответствии с документацией и, по-видимому, допускают желаемое поведение (блокирование новых клиентов). Это не помогло бы с частью «восходящий межсетевой экран / балансировщик нагрузки должен перенаправить» часть.

Последнее предположение: Можете ли вы (документация говорит да, но я не уверен, каковы будут последствия) переопределить адрес конечной точки (точек) по адресу localhost по требованию? Это может также работать как «Закрытие порта» для хоста брандмауэра, если он все равно не убивает всех клиентов.

Редактировать: играя с приведенными выше советами и ограниченным тестом, я начал играть с комбинацией инспектор сообщений / поведение, которая выглядит многообещающей на данный момент:

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
// Все остальные сообщения никогда не поступали на сервер и умирают с ошибкой
Ошибка в клиентском запросе после блока.
Ошибка в клиентском запросе после блока.
Ошибка в клиентском запросе после блока.
Ошибка в запросе клиента перед блокировкой.
Ошибка в запросе клиента перед блоком.

2 голосов
/ 21 декабря 2009

Есть ли API для вышестоящего брандмауэра? То, как мы делаем это в нашем приложении, - останавливает новые запросы, поступающие на уровне балансировки нагрузки, а затем, когда все запросы завершают обработку, мы можем перезапустить серверы и службы.

1 голос
/ 21 декабря 2009

Мое предложение состоит в том, чтобы установить EventHandler, когда ваша служба переходит в «состояние остановки», используйте метод OnStop. Установите EventHandler, указывающий, что ваша служба переходит в состояние остановки.

Ваш обычный цикл обслуживания должен проверять, установлено ли это событие, если он есть, возвращать «Служба останавливает сообщение» вызывающему клиенту и не позволять ему вводить вашу обычную процедуру.

Пока у вас все еще запущены активные процессы, дайте ему завершиться, прежде чем метод OnStop перейдет к уничтожению хоста WCF (ServiceHost.Close).

Другой способ - отслеживать активные вызовы, используя собственный счетчик ссылок. Затем вы узнаете, когда можно остановить узел службы, как только счетчик ссылок достигнет нуля, и, выполнив вышеописанную проверку, когда было инициировано событие остановки.

Надеюсь, это поможет.

0 голосов
/ 22 декабря 2009

В дополнение к ответу Мэтью Стиплз.

Большинство серьезных балансировщиков нагрузки, таких как F5 и т. Д., Имеют механизм, позволяющий определить, жив ли узел. В вашем случае, кажется, проверить, открыт ли определенный порт. Но альтернативные способы можно легко настроить.

Так что вы могли бы выставить напр. две службы: реальная служба, которая обслуживает запросы, и служба, подобная «сердцебиению». При переходе в режим обслуживания вы можете сначала отключить службу мониторинга, которая снимет нагрузку с узла, и отключить реальную службу только после завершения обработки всех запросов. Звучит немного странно, но может помочь в вашем сценарии ...

0 голосов
/ 22 декабря 2009

Может быть, вы должны установить

ServiceBehaviorAttribute и атрибут OperationBehavior. Проверьте это на MSDN

0 голосов
/ 21 декабря 2009

Я думаю, что вам может потребоваться написать собственную реализацию с помощью вспомогательного класса, который отслеживает все выполняющиеся запросы, а затем, когда запрашивается завершение работы, вы можете узнать, все ли еще работает, отложить завершение работы на основании этого ... (может быть, с помощью таймера?)

Не уверен насчет блокировки дальнейших входящих запросов ... у вас должна быть глобальная переменная, которая сообщает вашему приложению, было ли запрошено завершение работы, и чтобы вы могли отклонить дальнейшие запросы ...

Надеюсь, это поможет вам.

0 голосов
/ 21 декабря 2009

Служба WCF аутентифицирует пользователя каким-либо образом? Есть ли у вас метод "рукопожатия"?

0 голосов
/ 17 декабря 2009

Я не реализовал это сам, так что YMMV, но я верю, что вы хотите приостановить службу до полной ее остановки. Приостановка может использоваться для отказа в новых подключениях при выполнении существующих запросов.

В .NET кажется, что подход к приостановке службы заключается в использовании ServiceController .

...