WCF, BasicHttpBinding: остановка новых подключений, но продолжение существующих соединений

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

У меня есть служба WCF, размещенная в службе Windows. Когда служба 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() для каждого - сразу закрывает клиентов.
  • Override ServiceHost.OnClosing() - позволяет мне задерживать Close, пока не решит, что это нормально закрыть, но в это время разрешены новые подключения.
  • Удалите конечную точку, используя описанную здесь технику . Это уничтожает все.
  • Запуск сетевого сниффера для наблюдения за ServiceHost.Close(). Хост просто закрывает соединение, ответ не отправляется.

Спасибо

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

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

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

Ответ 1

Гадание:

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

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

  • 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) {           
    }
}

Забудьте об использовании crappy lock и т.д., но используя это с очень простым тестом 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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

Возможно, вы должны установить

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

Ответ 8

В дополнение к ответу от Матфея Шпиль.

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

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