...
...

Распределенные вычисления с минимальными затратами

Распределенные вычисления с минимальными затратами Должно быть, каждому, кто постоянно читает компьютерную прессу, доводилось слышать о распределенных вычислениях. Теперь для взлома криптографических алгоритмов, предсказания погоды, поиска внеземных цивилизаций и решения прочих, требующих колоссальнейших вычислительных ресурсов, задач нет необходимости иметь суперкомпьютеры, стоящие многие миллионы долларов. Интернет дал возможность с ничтожными финансовыми затратами получить решения таких задач в приемлемые сроки, что достигается путем распределения вычислений между компьютерами, которые подключены к Всемирной Сети. В этой статье рассказывается о том, как с помощью языка Java можно разработать программу (Java-апплет), реализующую распределенный поиск решения задач, требующих большого объема вычислений. При этом подразумевается, что сделать это необходимо без каких-либо затрат на хостинг программы, т.е. используя бесплатные сервисы Интернета.

Для начала следует сказать несколько слов о том, почему автор рекомендует использовать именно Java-апплеты.
1. Язык Java был создан специально для того, чтобы разрабатывать программы для Интернета, а потому механизмы, связанные с взаимодействием компьютеров посредством Сети, очень просты как для понимания, так и для реализации.
2. Апплеты легко встраиваются в HTML-документы и, как правило, для запуска не требуют, чтобы пользователь произвел какие-либо специальные действия (скажем, кликнул по ссылке для того, чтобы скачать программу распределенных вычислений). Более того, если пользователя не предупреждать, что на его компьютере производятся какие-то вычисления, то он, скорей всего, даже ничего не заметит.
3. Апплеты выполняются на компьютере пользователя, а не на сервере, что позволяет использовать мощь тысяч компьютеров-клиентов, а не одного загруженного сервера.
4. Апплеты, созданные на Java, работают на любой платформе и под любой операционной системой, благодаря чему исчезает проблема, которая могла бы значительно сузить круг потенциальных поставщиков вычислительных ресурсов, — проблема совместимости.
Кто-то может сказать, что использование элементов ActiveX предпочтительнее использования апплетов, которые проигрывают в быстродействии, поскольку представляют собой байт-код, выполняемый только после трансляции в инструкции процессора. Тут необходимо заметить, что элементы ActiveX легко могут выполнять деструктивные действия, тогда как для апплетов это практически невозможно. По этой причине в целях безопасности в браузерах многих пользователей возможность загрузки элементов ActiveX отключена. К тому же, благодаря современным JIT-компиляторам, оптимизирующим выполнение байт-кода, производительность апплетов практически не уступает производительности программ, состоящих непосредственно из инструкций процессора.

Итак, алгоритм программы, производящей распределенные вычисления, имеет следующий вид:
1. при запуске программа считывает исходные данные из определенной области, принадлежащей Всемирной Сети;
2. до тех пор, пока возможно выполнение программы, она производит требуемые вычисления;
3. перед завершением программа записывает результаты вычислений в ту же область, откуда были получены исходные данные, что позволит другим программам не производить те вычисления, которые уже были выполнены.
В статье не будет обсуждаться, как все множество вычислительных операций должно быть разбито на подмножества с тем, чтобы разделить их между многими компьютерами. Это существенно зависит от типа решаемой задачи и, как правило, не является сложной проблемой. Остановимся на вопросах реализации считывания и записи данных средствами, предоставляемыми языком Java.
Как известно, для того чтобы сделать апплеты безопасными, на них были наложены некоторые ограничения. В частности, апплеты, загружаемые по сети, не могут работать с файлами, а также устанавливать произвольные сетевые соединения. Из-за этих особенностей возникают трудности в реализации первого и третьего пунктов представленного выше алгоритма. Тот, кто имеет возможность запускать на сервере, подключенном к Интернету, различные программы (как правило, такой сервис стоит денег), решает эти трудности, используя технологию клиент-сервер: при запуске апплет запрашивает у сервера исходные данные, а перед завершением передает ему результаты вычислений, которые сервер может сохранить где угодно. Обычный же пользователь Интернета, который не желает тратить деньги на хостинг, имеет возможность разместить на сервере лишь HTML-документы, Java-апплеты и файлы с картинками и анимацией. Плюс еще он получает бесплатный почтовый сервис. К счастью, этого достаточно для того, чтобы производить распределенные вычисления, поскольку апплет может использовать почтовый ящик для считывания исходных данных и записи полученных результатов (апплету разрешается устанавливать соединения с хостом, с которого он был загружен).
Для того чтобы работать с почтой, используя язык Java, необходимо знать два протокола: POP3 (Post Office Protocol) и SMTP (Simple Mail Transfer Protocol). Далее будет рассмотрена та часть этих протоколов, которой достаточно для разработки процедур считывания и записи данных, хранящихся в почтовом ящике.
Протокол POP3 используется для получения электронных сообщений, находящихся в почтовом ящике. Этот процесс разделен на три стадии: авторизацию, транзакцию и обновление. После установки соединения с почтовым сервером начинается стадия авторизации, при которой клиент идентифицирует себя для сервера. Если авторизация прошла успешно, сервер открывает почтовый ящик клиента, и начинается стадия транзакции. На этой стадии клиент может либо запросить у сервера информацию (к примеру, список почтовых сообщений), либо заставить совершить его определенное действие (к примеру, выдать почтовое сообщение). На стадии обновления сеанс связи завершается. Ознакомимся подробней с тем, как можно работать с POP3 посредством стандартных средств языка Java. Для этого можно воспользоваться классом java.net.Socket. Сначала, используя этот класс, необходимо установить TCP-соединение с сервером, на котором будут располагаться апплет и почтовый ящик (номер порта протокола POP3 — 110):

Socket homepage = new Socket("homepage.server.by", 110);

В случае успешного завершения (необходимо не забывать обрабатывать исключительные ситуации), для того чтобы облегчить написание программы, следует надстроить потоки ввода и вывода, возвращаемые методами getInputStream() и getOutputStream(), другими потоковыми объектами, а именно DataInputStream и PrintStream соответственно:

DataInputStream input = new DataInputStream(homepage.getInputStream());
PrintStream output = new PrintStream (homepage.getOutputStream());


Теперь однострочное сообщение, получаемое от сервера, можно считать с помощью метода readLine() объекта input:

String s = input.readLine();

Передать же однострочное сообщение серверу можно следующим образом:

output.println("Привет!");

После того, как рассмотрены технические особенности, перейдем к командам POP3. На все команды сервер отвечает либо "+OK" (позитивный ответ) либо "-ERR" (негативный ответ). Оба ответа подтверждают, что обращение к серверу произошло. Как правило, за каждым ответом следует его содержательное словесное описание.
Итак, соединение с сервером установлено, началась стадия авторизации. Клиент посылает команду USER, добавив к нему название почтового ящика. Если ответ сервера будет "+OK", нужно послать команду PASS с паролем к этому ящику:

Клиент: USER data
Сервер: +OK
Клиент: PASS password
Сервер: +OK В почтовом ящике data есть 2 сообщения (320 байтов)


Далее начинается стадия транзакции. В следующих примерах демонстрируется возможный обмен сообщениями на этой стадии.
Команда STAT возвращает количество сообщений и количество байт в сообщениях:

Клиент: STAT
Сервер: +OK 2 320


Команда LIST без параметров возвращает список сообщений в почтовом ящике и их размеры:

Клиент: LIST
Сервер: +OK 2 сообщения (320 байтов)
Сервер: 1 120
Сервер: 2 200
Сервер: .


Команда LIST с номером сообщения возвращает информацию о заданном сообщении:

Клиент: LIST 2
Сервер: +OK 2 200...
Клиент: LIST 3
Сервер: -ERR Нет такого сообщения, в почтовом ящике только 2 сообщения


Команда RETR возвращает текст сообщения с указанным номером:

Клиент: RETR 1
Сервер: +OK 120 байтов
Сервер: Текст сообщения
Сервер: .

Команда LAST возвращает наибольший номер сообщения из тех, к которым ранее уже обращались:

Клиент: LAST
Сервер: +OK 2


Команда NOOP не возвращает никакой полезной информации, за исключением позитивного ответа сервера. Однако ответ означает, что сервер находится в соединении с клиентом и ждет запросов:

Клиент: NOOP
Сервер: +OK


Команда DELE помечает сообщение, которое требуется удалить:

Клиент: DELE 1
Сервер: +OK Сообщение 1 удалено
Клиент: DELE 2
Сервер: -ERR Сообщение 2 уже удалено


Команда RSET снимает метки удаления со всех отмеченных ранее сообщений:

Клиент: RSET
Сервер: +OK В почтовом ящике 2 сообщения (320 байтов)

Команда QUIT завершает сеанс связи с сервером:

Клиент: QUIT
Сервер: +OK Сеанс связи завершен


Следует обратить внимание на то, что отмеченные для удаления сообщения не удаляются до тех пор, пока не началась стадия обновления, которая инициируется командой QUIT. В любой момент в течение стадии транзакции клиент имеет возможность выдать команду RSET — и все отмеченные для удаления сообщения будут восстановлены.
Завершив обмен данными с сервером, необходимо проделать небольшую работу по удалению мусора:

output.close();
input.close();
homepage.close();


Теперь, когда работа с протоколом POP3 рассмотрена, перейдем к протоколу SMTP, который позволяет отправлять электронные сообщения. Установка соединения, а также работа с потоками ввода и вывода могут осуществляться так же, как и с POP3, за исключением того, что номер порта должен быть не 110, а 25.
Для ознакомления с SMTP возьмем следующий пример:

1. Сервер: 220 Сервис SMTP готов к работе
2. Клиент: HELO homepage.server.by
3. Сервер: 250 server.by
4. Клиент: MAIL FROM:<errors@homepage.server.by>
5. Сервер: 250 OK
6. Клиент: RCPT TO:<data@homepage.server.by>
7. Сервер: 250 OK
8. Клиент: DATA
9. Сервер: 354 Начат прием данных
10. Клиент: 1-я строка сообщения
11. Клиент: ...
12. Клиент: n-я строка сообщения
13. Клиент: .
14. Сервер: 250 OK
15. Клиент: QUIT
16. Сервер: 221 Сервис SMTP завершил работу


Рассмотрим подробно этот пример. В начале каждой строки, переданной сервером, следует трехзначная цифровая комбинация, которая обозначает код ответа (значения кодов поясняются далее). После того, как соединение установлено, сервер отвечает кодом 220 (строка 1), что означает, что соединение установлено успешно и сервер готов принимать сообщения.
Первой командой, которую клиент передает серверу, должна быть команда HELO, после которой следует имя клиента (строка 2). На это сервер отвечает кодом 250 (строка 3), сообщая о том, что команда принята и обработана.
После идентификации посредством команды HELO клиент приступает к транзакции. Для этого он выполняет команду MAIL (строка 4), которая имеет следующий синтаксис: "MAIL <пробел> FROM: <адрес отправителя> <CRLF> ". Здесь "<CRLF> " — это два символа, которые означают возврат каретки и переход на следующую строку. Адрес отправителя указывает серверу, куда в случае ошибки отослать соответствующее сообщение. На команду MAIL сервер отвечает, что все в порядке (строка 5).
Далее с помощью команды RCPT клиент указывает адрес получателя сообщения (строка 6). Команда RCPT имеет синтаксис, подобный команде MAIL: "RCPT <пробел> TO: <адрес получателя> <CRLF> ". Если адрес получателя правильный, то сервер отвечает, как показано в строке 7. В противном случае он выдал бы ответ, подобный "550 Неверный адрес получателя".
После того, как передан адрес получателя, можно передавать содержание сообщения. Клиент передает команду DATA (строка 8), на что сервер отвечает, что готов к приему (строка 9).
Строки 10, 11 и 12 — это содержание сообщения, которое может иметь вид как обычного текста, так и двоичных данных. В качестве признака того, что передача сообщения завершена, используется комбинация "<CRLF> .<CRLF> " (строка 13). Когда сервер получает эту комбинацию, он завершает прием сообщения и выдает ответ (строка 14).
Для завершения сеанса связи с сервером необходимо передать команду QUIT (строка 15) и убедиться, что сеанс действительно завершен (строка 16).
Рассмотрим теперь коды ответов сервера. Каждая цифра в коде ответа имеет определенный смысл. Первая цифра означает, было ли выполнение команды успешно (2), неуспешно (5) или еще не закончилось (3). Поскольку для решения проблемы, обсуждаемой в статье, программе распределенных вычислений достаточно анализировать только первую цифру и на основании ее продолжать свои действия, то более подробно коды ответов здесь рассматриваться не будут. Иногда сервер выдает ответ, состоящий из нескольких строк. В таком случае во всех строках, кроме последней, после кода ответа следует не пробел, а дефис:

Сервер: 250-1-я строка ответа
Сервер: 250-2-я строка ответа
Сервер: 250 3-я строка ответа


Это необходимо для того, чтобы клиент мог отличить строку-продолжение от последней строки.
Мы рассмотрели ту часть протоколов POP3 и SMTP, которая необходима для решения проблемы считывания/записи данных, используемых в работе Java-апплета. Если кому-то понадобится более подробная информация об этих протоколах, то они смогут получить ее, обратившись к RFC 1225 и RFC 821.
Итак, Java-апплет, выполняющий распределенные вычисления, готов. Что же дальше? А дальше достаточно поместить его на сервере и добавить в какую-нибудь веб-страницу тэг APPLET. Теперь при каждой загрузке этой страницы в браузере пользователя будет запускаться и апплет (если, конечно, браузер это позволяет). И пока пользователь не покинет страницу, апплет может производить вычисления, используя ресурсы компьютера посетителя. Чем больше будет посетителей, тем большую вычислительную мощность будет иметь эта система.

Сергей Иванчегло,
come-from-beyond@mail.ru



© Компьютерная газета

полезные ссылки
IP камеры видеонаблюдения