WCF и переподключение
От: Слава  
Дата: 03.03.15 08:26
Оценка: 18 (2)
Здравствуйте.

Есть десктопный клиент и сервер WCF на C#, .NET4.5, TCP-соединение.

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

Я хочу сделать так, чтобы клиент сам подключался к серверу, после того, как соединение один раз было успешно установлено. Как наилучшим образом это реализовать?

Есть еще один клиент, который переподключается сам, но там я это сделал довольно-таки сложным способом — создал класс, методы которого внутри себя используют несколько вызовов к сервису, и на каждый публичный метод есть внутренний метод-исполнитель

Выглядит это так
  Один из публичных методов, его вызывают из GUI и прочих таких мест
        public CallResult<SideTestEvent> TestSide(int sideId)
        {
                return ExecuteTask(ApplyPartial<int, CancellationToken, CallResult<SideTestEvent>>(InternalTestSide, sideId), _operationTimeout);
        }

  Внутренний метод, делает всю работу с WCF-сервисом
        public CallResult<SideTestEvent> InternalTestSide(int sideId, CancellationToken ct)
        {
            _logger.Debug("Worker InternalTestSide {0}", sideId);

            var dispensersTask = _proxy.GetCashedDispensersAsync();
            dispensersTask.Wait(ct);

            if (dispensersTask.IsCompleted)
            {
                var dispensers = dispensersTask.Result.ToArray();
                _logger.Debug("Worker InternalTestSide {0} LogDispensers", sideId);
                //LogDispensers(dispensers);

                var connected = dispensers.Where(_ => _.SideId == sideId && _.IsConnected).ToArray();

                if (connected.Length == 0)
                {
                    return CallResult<SideTestEvent>.Ok(new SideTestEvent {Result = SideTestResult.SideOutOfService});
                }
            ...skipped...
            }
            return CallResult<SideTestEvent>.Fail();
        }

  Одна из похожих функций, принимающих на вход функцию с кучей аргументов и возвращающая ее же, с одним новым аргументом, а старые запомнены (карринг, но не совсем)
        private static Func<T2, TResult> ApplyPartial<T1, T2, TResult>(Func<T1, T2, TResult> function, T1 arg1)
        {
            return (b) => function(arg1, b);
        }

  Обертка, исполняющая внутренние методы, с указанным таймаутом и перезапуском соединения в случае ошибки
        private CallResult<T> ExecuteTask<T>(Func<CancellationToken, CallResult<T>> action, TimeSpan operationTimeout, [CallerMemberName] string callerName = "ExecuteTask")
        {            
            CallResult<T> result;

            var needReconnect = false;

            if (_state == SupervisorState.Work && _proxy != null)
            {
                if (_proxy.State == CommunicationState.Opened)
                {
                    using (var cts2 = CancellationTokenSource.CreateLinkedTokenSource(new[] {_cts.Token}))
                    {
                        var token = cts2.Token;
                        var task = Task.Factory.StartNew(() =>
                        {
                            try
                            {
                                return action(token);
                            }
                            catch (Exception exc)
                            {
                                _logger.DebugException("Service " + callerName, exc);
                                return CallResult<T>.Fail(exc.Message);
                            }
                        }, cts2.Token);
                        task.Wait((int) operationTimeout.TotalMilliseconds, cts2.Token);
                        if (task.IsCompleted)
                        {
                            result = task.Result;
                        }
                        else
                        {
                            cts2.Cancel();
                            result = CallResult<T>.Fail("Сервер не ответил за отведенное время");
                            needReconnect = true;
                        }
                    }
                }
                else
                {
                    result = CallResult<T>.Fail("Нет связи с сервером");
                    needReconnect = true;
                }
            }
            else
            {
                result = CallResult<T>.Fail("Нет связи с сервером");
                _lastAttachError = "Нет связи с сервером";
            }

            if (needReconnect)
            {
                Reconnect();
            }
            
            return result;
        }

  Перезапуск соединения
        private void Reconnect()
        {
            var iAmFirst = false;
            lock (_lock)
            {
                if (_state != SupervisorState.Connecting)
                {
                    iAmFirst = true;
                    _state = SupervisorState.Connecting;
                }
            }
            if (iAmFirst)
            {
                InternalDetach();
                _logger.Debug("Service Reconnect");
                Task.Factory.StartNew(ConnectLoop, TaskCreationOptions.LongRunning);
            }
        }

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

Проблема в том, что даже для использования в моем коде вся эта конструкция какая-то излишне громоздкая, трудоемкая. В рабочем же месте, для которого я хочу сделать нечто подобное, используется не 10 методов WCF-сервиса, а все 250. Делать ко всему этому обертки в таком стиле будет очень утомительно. Кто что может порекомендовать?

  Дополнение
Если кто захочет написать про быдлокод — не стесняйтесь, когда это писалось, я был мало знаком с WCF
Отредактировано 03.03.2015 8:28 Слава . Предыдущая версия . Еще …
Отредактировано 03.03.2015 8:28 Слава . Предыдущая версия .
Re: WCF и переподключение
От: Sharov Россия  
Дата: 03.03.15 09:57
Оценка:
Здравствуйте, Слава, Вы писали:

С>Проблема в том, что даже для использования в моем коде вся эта конструкция какая-то излишне громоздкая, трудоемкая. В рабочем же месте, для которого я хочу сделать нечто подобное, используется не 10 методов WCF-сервиса, а все 250. Делать ко всему этому обертки в таком стиле будет очень утомительно. Кто что может порекомендовать?


Попробуйте посмотреть на "Smart Client Architecture and Design Guide". Это вероятно не совсем то, что Вам нужно,
но какие-то идеи и другие ключевые слова можно подсмотреть. Исчо можно загуглить "wcf reconnect automatically".
Т.е. в общем случае надо будет написать единый универсальный метод (ну или несколько -- по кол-ву параметров),
и работать с InnerChannel , channelfactory и т.д.
Кодом людям нужно помогать!
Re: WCF и переподключение
От: scale_tone Норвегия https://scale-tone.github.io/
Дата: 03.03.15 21:37
Оценка:
Здравствуйте, Слава, Вы писали:

С>Здравствуйте.


С>Проблема в том, что даже для использования в моем коде вся эта конструкция какая-то излишне громоздкая, трудоемкая. В рабочем же месте, для которого я хочу сделать нечто подобное, используется не 10 методов WCF-сервиса, а все 250. Делать ко всему этому обертки в таком стиле будет очень утомительно. Кто что может порекомендовать?


Первая известная мне попытка раз и навсегда решить проблему connectivity для WCF-ных клиентов называлась The Disconnected Service Agent Application Block. Судя по "This content is outdated and is no longer being maintained", попытка не очень удачная, да и не мудрено: столь наукоемкий инструмент в руках эээ... начинающих разработчиков Enterpise-приложений приводил к самым удивительным природным эффектам типа 100500 раз оплаченных счетов (инд... кодер не задизаблил кнопку, сеть отвалилась, бухгалтерша кнопку изнасиловала, запросы к сервису выстроились в очередь и потом все разом выполнились).

Нынче многие "идущие в ногу со временем" разработчики предпочитают Castle Windsor и его WCF Facility, а автореконнект реализуют с помощью кастомного LifestyleManager-a. Я, впрочем, как потомственный ретроград, этому поделию не доверяю — не понимаю, как можно юзать либу без документации, зато с десятками форков.

Наверняка нагуглится куча аналогов этого решения, с разными степенями залезания в недра WCF. Но подумайте хорошо, а точно ли оно Вам надо?
Может, достаточно будет выставить ReliableSession в true?
Или перейти на байндинг с гарантированной доставкой?
Re[2]: WCF и переподключение
От: Слава  
Дата: 04.03.15 08:08
Оценка:
Здравствуйте, scale_tone, Вы писали:

_>Наверняка нагуглится куча аналогов этого решения, с разными степенями залезания в недра WCF. Но подумайте хорошо, а точно ли оно Вам надо?

_>Может, достаточно будет выставить ReliableSession в true?
_>Или перейти на байндинг с гарантированной доставкой?

Конкретно по этим рекомендациям — я не понимаю, как в моем случае это можно использовать. Запущен сервер, у него есть некое состояние, которое индивидуально для каждого подключенного клиента. Когда клиент подключается, это состояние создается, и некоторыми действиями клиента изменяется. Состояние это активно, пока запущен сервер, когда сервер перезапущен, состояние теряется, его нужно восстановить заново, действиями клиента.

Можно было бы состояние сериализовать, но — а как его привязать к клиенту? ReliableSession — у сессии есть ID, и я как-то могу его получить? То есть, сервер был перезапущен, перед остановкой он сохранил все состояния с ID соответствующих сессий, после запуска восстановил, клиент вновь подключился с неким ID сессии, состояние вновь привязывается к соединению — и все отлично. А если сервер помер ВНЕЗАПНО, и состояние сохранить не смог? Нужно уведомить клиента, что сессия невалидна и не восстановится, давай — подключайся заново, и мы возвращаемся к исходной задаче.
Re[3]: WCF и переподключение
От: scale_tone Норвегия https://scale-tone.github.io/
Дата: 04.03.15 20:32
Оценка:
Здравствуйте, Слава, Вы писали:

С>Можно было бы состояние сериализовать, но — а как его привязать к клиенту? ReliableSession — у сессии есть ID, и я как-то могу его получить? То есть, сервер был перезапущен, перед остановкой он сохранил все состояния с ID соответствующих сессий, после запуска восстановил, клиент вновь подключился с неким ID сессии, состояние вновь привязывается к соединению — и все отлично. А если сервер помер ВНЕЗАПНО, и состояние сохранить не смог? Нужно уведомить клиента, что сессия невалидна и не восстановится, давай — подключайся заново, и мы возвращаемся к исходной задаче.


Я, вообще-то, ReliableSession=true предлагал только в качестве средства борьбы с мелкими сетевыми проблемами. От рестарта сервера оно почти наверняка не поможет.
Re: WCF и переподключение
От: WolfHound  
Дата: 04.03.15 23:00
Оценка:
Здравствуйте, Слава, Вы писали:

С>Проблема в том, что даже для использования в моем коде вся эта конструкция какая-то излишне громоздкая, трудоемкая. В рабочем же месте, для которого я хочу сделать нечто подобное, используется не 10 методов WCF-сервиса, а все 250. Делать ко всему этому обертки в таком стиле будет очень утомительно. Кто что может порекомендовать?

На немерле можно легко написать макрос который это делает.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.