Прохожу курс "Построение распределенных систем на Java" в третьей лекции где описывается TCPServer вылетает эта ошибка
"Connection cannot be resolved to a type" Java version 1.7.0_05 |
Использование Java RMI
Компиляция и выполнение сервера и клиента
Подготовив отдельные фрагменты, мы можем сформировать и выполнить наше распределенное приложение, но для этого потребуется несколько действий. Для начала необходимо компилировать исходные классы. Далее, нужно компилировать класс удаленного объекта ( ...Impl ), используя компилятор rmic (утилита J2SE ) для формирования класса-заглушки (о котором говорилось в предыдущем разделе). Этот класс должен быть доступен для клиента (либо локально, либо путем загрузки по сети), чтобы дать возможность устанавливать удаленное соединение с серверным объектом. В зависимости от параметров командной строки, передаваемых rmic,может быть сгенерировано несколько файлов. В Java 1.1 rmic формирует два класса - класс-заглушку и класс-каркас ( skeleton ). В Java 2 класс-каркас больше не требуется. Параметр командной строки -v1.2 указывает, что rmic следует создать только класс-заглушку.
Для нашего примера командная строка для компиляции класса удаленного объекта будет выглядеть следующим образом:
rmic -v1.2 com.asw.rmi.ex1.BillingServiceImpl
которая сгенерирует файл BillingServiceImpl_Stub.class.
Следующий этап - запуск реестра RMI, который зарегистрирует удаленный объект. Командная строка
rmiregistry
запускает реестр RMI на локальной машине. В окне командной строки в ответ на эту команду никакого текста отображаться не будет. Типичная ошибка заключается в том, что если не запустить реестр RMI прежде чем попытаться привязать удаленный объект к реестру, будет сгенерировано исключение java.rmi.ConnectException,которое указывает, что программа не может соединиться с реестром.
Чтобы удаленный объект мог принимать удаленные вызовы методов, необходимо связать объект с именем в реестре RMI. Для этого нужно запустить серверное приложение из командной строки. В нашем случае командная строка выглядит так:
java com.asw.rmi.ex1.BillingServiceImpl
В результате отображается сообщение об инициализации BillingService.
Теперь клиентское приложение может соединиться с удаленным объектом, выполняющимся на локальной машине localhost. Команда
java com.asw.rmi.ex1.BillingClient
соединит BillingClient с объектом BillingServiceImpl.
Если серверное приложение выполняется не на клиенте, можно указать IP -адрес или доменное имя компьютера-сервера в качестве параметра командной строки при выполнении клиента. Например, чтобы осуществить доступ к компьютеру с IP -адресом 192.168.1.1, введем команду
java com.asw.rmi.ex1.BillingClient 192.168.1.1
Несколько слов о синхронизации
Теперь, после того как нами реализовано первое приложение с использованием RMI, настало время немного поговорить об одной из особенностей, связанной с применением этой технологии.
Дело в том, что при вызове клиентом метода удаленного объекта исполняющей частью системы на стороне сервера формируется (или выбирается из числа уже созданных) поток, в котором и происходит вызов. Таким образом, становится возможным одновременное выполнение методов сервера несколькими клиентами.
Непонимание этого факта может привести к ошибкам при программировании серверных объектов, которые затем будет очень сложно локализовать и устранить. Для иллюстрации рассмотрим один из возможных сценариев выполнения нашего приложения, в случае если два клиента одновременно вызывают метод addMoney для одной и той же карты (на приведенной схеме (таблица 5.1) события разворачиваются во времени - время течет сверху вниз):
Итак, в момент t1 оба клиента вызывают метод addMoney для карты с номером 1 и с разными суммами начисления (мы предполагаем, что карта с таким номером существует). Далее по каким-то причинам выполнение потоков, в которых происходит работа методов addMoney для первого и второго клиентов, происходит с разными скоростями. В момент времени t2 первый клиент получает из хэша значение баланса карты - второй делает то же самое. В момент времени t3 первый клиент изменяет значение баланса, прибавляя к нему свое начисление, и возвращает баланс в хэш. Ту же самую операцию, но только в момент времени t4 - позднее, чем первый - делает второй клиент. В результате приведенного сценария в хэше окажется значение баланса, измененное вторым клиентов, а изменения, которые сделал первый клиент, просто потеряются - т.е. созданное нами приложение отработает неправильно.
Эффект, в результате которого результат работы приложения зависит от скорости выполнения потоков (фактически - от последовательности действий), в литературе получил название "гонка потоков". Об этой и других проблемах, связанных с разработкой "параллельных" приложений, речь пойдет ниже, в соответствующем разделе. Пока же, для того чтобы обеспечить работоспособность нашего приложения, попробуем добавить ключевое слово synchronized в описание метода addMoney,получив следующее объявление (пример 5.5).
public synchronized void addMoney(String card, double money) throws RemoteException { Double d = (Double)hash.get(card); if (d!=null) hash.put(card,new Double(d.doubleValue()+money)); else throw new NotExistsCardOperation(); }Листинг 5.5.
Таким образом, будет гарантировано то, что в указанный метод одномоментно сможет войти только один поток - приведенный сценарий станет невозможным1Очевидно, для других серверных методов нужно сделать то же самое .
Может показаться, что, введя синхронизацию на уровне методов, мы решили проблему, однако это не так. Чтобы убедиться в этом, достаточно рассмотреть одновременное выполнение двух разных методов - addMoney и subMoney - с теми же самыми предположениями о скоростях выполнения. Несложно догадаться, что в приведенном примере блокировка должна выполняться не на уровне методов, а на уровне ресурсов - в нашем случае хэш-таблицы. Для окончательного устранения нежелательного эффекта код всех методов изменения баланса должен быть переписан как-нибудь так2Поступив таким образом, мы фактически превратили наше приложение в строго последовательное. В действительности нам вовсе не обязательно было синхронизировать доступ ко всей хэш-таблице, достаточно было бы синхронизации доступа к конкретной карте - при этом операции с разными картами могли бы выполняться параллельно (пример 5.6).
public void addMoney(String card, double money) throws RemoteException{ synchronized (hash) { Double d = (Double) hash.get(card); if (d != null) hash.put(card, new Double(d.doubleValue() + money)); else throw new NotExistsCardOperation(); } }Листинг 5.6.