Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Как WCF служба может узнать, что клиент отсоединился?

#c_sharp #wcf


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

Так вот, если неожиданно клиент отрубился(пропал интернет и все такое), как WCF служба
может узнать, что клиента, который производил редактирование, больше нет и нужно вернуть
статус на доступно?

А что если деструктор на стороне службы сделать? По идее если клиент не отвечает
долгое время, то его экземпляр умирает и тут в дело приходит мой деструктор , который
разблокирует записи, если такие есть
    


Ответы

Ответ 1



Для этих целей лучше использовать класс IChannelInitializer - класс, предназначенный для детектирования подключения клиентов, в котором вы можете установить события на закрытие канала(чтобы не писать их в каждой реализации метода службы) Например, создадим класс ClientTrackerChannelInitializer, который наследует интерфейс IChannelInitializer. class ClientTrackerChannelInitializer : IChannelInitializer { internal static int ConnectedClientCount = 0; // метод, который определяет, что клиент подсоединился, создался новый канал public void Initialize(IClientChannel channel) { ConnectedClientCount++; Console.WriteLine("Client {0} initialized", channel.SessionId); channel.Closed += ClientDisconnected; channel.Faulted += ClientDisconnected; } // событие, на которое подписались при создании канала static void ClientDisconnected(object sender, EventArgs e) { Console.WriteLine("Client {0} disconnected", ((IClientChannel)sender).SessionId); ConnectedClientCount--; } } У него два метода Initialize - метод, который срабатывает при подключении нового клиента ClientDisconnected - метод, обработчик события закрытия канала(или ошибки в канале) Теперь необходимо подключить класс ClientTrackerChannelInitializer на стороне сервера. Для этого создадим класс, реализующий интерфейс IEndpointBehavior, класс, который позволяет расширять поведение конечной точки службы class ClientTrackerEndpointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { // подключаем наш класс endpointDispatcher.ChannelDispatcher.ChannelInitializers.Add(new ClientTrackerChannelInitializer()); } public void Validate(ServiceEndpoint endpoint) { } } В методе ApplyDispatchBehavior мы подключили класс ClientTrackerChannelInitializer. Все классы созданы. Теперь можно публиковать службу. Предположим, что у нас есть сервис IStackCalculator. Класс StackCalculator реализует данный сервис [ServiceContract(SessionMode = SessionMode.Required)] public interface IStackCalculator { [OperationContract] void Enter(double value); [OperationContract] double Add(); [OperationContract] double Subtract(); [OperationContract] double Multiply(); [OperationContract] double Divide(); } Теперь при публикации хоста сервиса подключим наше расширение конечной точки. string baseAddress = "http://" + Environment.MachineName + ":8000/Service"; ServiceHost host = new ServiceHost(typeof(StackCalculator), new Uri(baseAddress)); WSHttpBinding binding = new WSHttpBinding(SecurityMode.None); binding.ReliableSession.Enabled = true; ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IStackCalculator), binding, ""); // подключаем расширение конечной точки службы endpoint.Behaviors.Add(new ClientTrackerEndpointBehavior()); host.Open(); Console.WriteLine("Host opened"); Теперь при соединении/отключении клиентов будут срабатывать события Initialize и ClientDisconnected нашего класса ClientTrackerChannelInitialize. Нет необходимости прописывать подписывание на событие закрытия канала в самой реализации метода. Механизм создания классов-расширения служб, реализующих интерфейс IEndpointBehaviour, можно применять для многих целей. По своему опыту применял для подсчета количества вызовов функций сервиса, для замера скорости выполнения функции сервиса, для аутентификации пользователя... Посмотрите Примеры использования IEndpointBehavior. Более подробно о примере, который мы рассмотрели, можно прочитать здесь. Пример кода P.S. Если необходимо подключить кастомный IEndpointBehavior через файл config, напишите, добавлю в ответ. Edit: Чтобы подключить наш класс-расширение конечной точки ClientTrackerEndpointBehavior, мы должны сделать следующее Зарегистрировать наше поведение в < system.servicemodel >/< extensions >/< behaviorExtensions > указав имя расширения и тип класса, реализующего расширение(тип класса ClientTrackerEndpointBehavior) < extensions > < behaviorExtensions> < add name="clientTracker" type="ClientTrackerEndpointBehavior, Client.ExtensionsDLL /> < /behaviorExtensions> Добавить расширение конечной точки в элементе < behaviors >. Здесь мы в качестве расширения указываем элемент-расширение clientTracker, которое определили на предыдущем шаге < behaviors> < endpointBehaviors> < behavior name="ServiceBehaviorExtension"> < clientTracker /> < /behavior> < /endpointBehaviors> < /behaviors> Теперь при определении конечной точки сервиса мы можем подключить поведение через атрибут behaviourConfiguration, указав имя расширения, указанного выше < services> < service name="StackCalculator" > < endpoint name="endpoint1" address="" binding="basicHttpBinding" bindingName="binding" contract="IStackCalculator" behaviorConfiguration="ServiceBehaviorExtension" / > ... < /services > Пример можно посмотреть например отсюда

Ответ 2



Вам нужен элемент привязки reliableSession. Этот элемент привязки, помимо прочих функций, периодически посылает сообщения для поддержания соединения в активном состоянии. У элемента привязки reliableSession есть настройка inactivityTimeout. Она задает период времени, в течении которого этот элемент ожидает сообщений с другого конца соединения прежде чем сказать что соединение закрылось. При этом сам он периодически посылает "пустые" сообщения для поддержания соединения. Внимательнее будьте с настройкой receiveTimeout. Она задает интервал времени, в течении которого сервис ожидает хоть одного непустого сообщения. Значение здесь должно быть как минимум раз в десять выше, чем значение inactivityTimeout - иначе весь смысл reliableSession теряется. UPDATE Теперь как отслеживать соединение/отсоединение клиента. Самый простой способ - использовать [ServiceContract(SessionMode = SessionMode.Required)] и [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)], при этом реализовать интерфейс IDisposable. При подключении нового клиента будет вызван конструктор сервиса, при отключении (нормальном либо аварийном) - метод Dispose(). UPDATE Пример фрагмента конфига привязки: Достаточно настройки привязки, остальные настройки менять не нужно. Настройки привязки на клиенте и сервере должны совпадать (либо быть совместимы - но проще чтобы совпадали). Скорее всего, у вас binding будет иметь также атрибут name. Атрибут ordered="false" не обязателен, привел его для примера. Он отвечает за то, будет ли reliableSession следить за порядком сообщений.

Ответ 3



Как вариант клиенту дергать метод WCF службы, который говорит, что "вот я в сети" с периодичностью скажем 5-15 минут. Если инет падает у клиента, он метод не дергает, а значит служба через 5-15 минут меняет в БД статус. Дополнение к ответу Можно прочекать отвалился ли клиент так: using System; using System.ServiceModel; namespace WcfServiceLibrary1 { public class Service1 : IService1 { public string GetData(int value) { ClientConnected(); return string.Format("You entered: {0}", value); } private void ClientConnected() { IContextChannel objClientHandle = OperationContext.Current.Channel; objClientHandle.Faulted += new EventHandler(this.ClientDisconnected); } private void ClientDisconnected(object sender, EventArgs e) { var context = (IContextChannel)sender; // вот тут будет понятно, что клиент отрубился. // Тестил я так - установил таймаут на биндинге - называется он в конфиге. // Через 15 сек клиент отваливается и кидает сюда. } } } Вот на всякий случай как прописать таймаут, чтобы протестить, с реальным дисконнектом не тестил:

Комментариев нет:

Отправить комментарий