Как перехватить и авторизовать ввод пользователя с сервера - PullRequest
7 голосов
/ 16 августа 2011

Краткое введение:

У меня есть [физическая] среда моделирования, которая раньше была однопользовательской настольной версией Каркас служит инструментарием, например, для учителя для создания различных видов симуляций без глубоких знаний программирования на Java и / или специальных математических навыков. В конце концов возникла идея применить парадигму клиент-сервер к платформе, чтобы позволить нескольким клиентам взаимодействовать при работе с одной и той же симуляцией (= для синхронизации симуляции между всеми клиентами).

Некоторые дополнительные технические факты:

Каркас / моделирование разработаны на основе шаблона MVC.

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

Сама авторизация очень проста и в основном только на временных отметках определяет, следует ли принять изменение (чтобы избежать проблем, вызванных клиентами с разными задержками, вызывающими изменения в одной и той же вещи в [почти] одно и то же время).

Задача соответственно на вопрос:

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

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

Одно решение, которое я думал, могло бы сработать:

Я думал о введении нового интерфейса, например:

public interface Synchronizable {
  public boolean appliesChanges();
}

Ограничением в этом случае будет то, что слушатели изменений любого типа должны будут дополнительно реализовать этот интерфейс, если они хотят, чтобы события изменений, которые они слушают, были синхронизированы. Таким образом, базовая структура может заменить все объекты, реализующие Synchronizable, на прокси-объекты, отвечающие за проверку изменения [события] на сервере (и при успешной передаче события слушателю реальных изменений).

Идея метода applyChanges заключается в том, что не все вызовы прослушивателя изменений действительно приводят к изменению, которое требует синхронизации. Например, Swing JSlider может генерировать события всякий раз, когда ручка перемещается, но конкретная реализация прослушивателя изменений может применить реальное изменение только после отпускания ручки (то есть «значение больше не корректируется»). События изменений, происходящие между ними, не нужно отправлять на сервер, так как они все равно не влияют. Такой подход не был бы ни удобным, ни особенно красивым, но я не мог придумать никакой другой возможности решить проблему другим способом?!

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

Пример с описанием этой проблемы:

public interface SynchronizedChangeListener extends ChangeListener, Synchronizable {}
public interface SynchronizedPropertyChangeListener extends PropertyChangeListener, Synchronizable {}

public static void main(String[] args) {

  SynchronizedChangeListener scl = new SynchronizedChangeListener() {
    public void stateChanged(ChangeEvent e) {
      System.out.println("Hello world - SynchronizedChangeListener");
    }
    public boolean appliesChanges() {
      return true;
    }
  };
  SynchronizedPropertyChangeListener spcl = new SynchronizedPropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent evt) {
      System.out.println("Hello world - SynchronizedPropertyChangeListener");
    }
    public boolean appliesChanges() {
      return true;
    }
  };
}

Как прокси-слушатель узнает, что для PropertyChangeEvents он должен вызвать метод 'propertyChange', тогда как для ChangeEvents он должен вызвать метод 'stateChanged'? Способна ли рефлексия решить эту проблему?

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

Ответы [ 3 ]

0 голосов
/ 19 февраля 2013

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

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

Today:
--------                   ------------------                ----------
|  UI  | - change event -> | Event Listener | - set value -> | Server |
--------                   ------------------                ----------

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

Proposed:
--------                   ------------------            ------------------                ----------
|  UI  | - change event -> | Proxy Listener | - event -> | Event Listener | - set value -> | Server |
--------                   ------------------            ------------------                ----------
                                   |
                               timestamp
                                   |
                                   v
                               ----------
                               | Server |
                               ----------

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

Я думаю, что более общее решение, которое может быть применено к вашим текущим вариантам использования и любым будущим расширениям, будет заключаться в том, чтобы сделать вызов установленного значения безопасным для сервера. То есть Сервер должен определить, когда прослушиватель событий пытается внести изменения в значение, которое является актуальным (иначе: значение устарело), ​​и, в этом случае, отказаться от внесения этого изменения. В этом случае необходимо решить две небольшие проблемы: как обнаружить изменение в устаревшем состоянии и, во-вторых, как сообщить клиенту, что изменение не было выполнено.

Для первой проблемы вы можете захотеть реализовать некоторую форму оптимистической блокировки. Как и в предложенном вами решении, вы можете использовать временные метки для этого. Например, когда пользовательский интерфейс рендерится, он получит значение с сервера. Сервер может передать обратно значение, а также отметку времени последнего изменения этого значения. Когда пользователь вносит изменение, клиент передает новое значение на сервер и исходную последнюю измененную временную метку. (т. е. «Сервер, установите foo равным 10 и последний раз, когда кто-то изменил foo, был вчера.») Затем сервер узнает, соответствует ли ваше изменение самому последнему значению, и если нет, может отказаться от изменения.

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

(Терминологическое предупреждение: используете ли вы временную метку или хэш, это значение служит «мьютексом».)

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

Что касается второй проблемы, касающейся того, как уведомить клиента о том, что изменение было отклонено, поскольку оно устарело, это должно быть относительно легко в зависимости от того, как взаимодействуют ваш клиент и сервер. Если вы используете какой-либо SOAP или RPC, который допускает удаленные исключения, я бы рекомендовал вам отбросить что-то похожее на Java ConcurrentModificationException. В этом случае все, что вам нужно сделать, это просмотреть, перехватить исключение и обработать его соответствующим образом. Это, скорее всего, означает повторный вызов сервера для получения наиболее актуального значения и, вероятно, уведомление пользователя о том, что запрошенное обновление не было принято, поскольку состояние моделирования изменилось из-под них.

0 голосов
/ 02 декабря 2013

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

В других ответах рассматриваются проблемы множественного общения и возможность синхронизации с использованием мьютексов.

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

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

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

Реальное решение заключается в том, намереваетесь ли вы разрешить многоузловые обновления или ограничить изменения из одного источника. Это в основном определит алгоритм, протокол и реализацию.

0 голосов
/ 09 сентября 2011

Как я вижу, у вас есть пользовательский интерфейс, где пользователь может предоставить некоторые данные. У вас есть другой пользовательский интерфейс Simulation, который выполняет физическое моделирование. Ввод из пользовательского интерфейса пользователя должен передаваться другому только после того, как сервер его авторизует.

Пока все хорошо?

Теперь ваш Simulation UI должен в конечном итоге ждать событий с вашего сервера. Я думал о чем-то вроде этого:

  1. Подготовьте XML, который содержит изменения, которые пользователь хочет получить от пользовательского интерфейса.
  2. Реализация простого сервлета (или распорок) на сервере Tomcat для анализа этого XML и ответа пользователю, который запросил его с авторизацией. Ответ также может быть в формате XML.
  3. Верните эту авторизацию обратно в интерфейс моделирования через очередь. Пользовательский интерфейс симуляции должен прослушивать в этой очереди все события, помещенные в нее.
  4. Это касается однопользовательского сценария. Для нескольких пользователей, получающих события с сервера, самый простой способ сделать это - опрос. Реализуйте таймер на уровне перед пользовательским интерфейсом моделирования, который будет опрашивать сервер. Сервер может ответить последними изменениями. Теперь введите это в очередь пользовательского интерфейса симуляции.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...