В данной главе мы обсудим очень интересную и обладающую большим практическим потенциалом тему – работу с «локальной сетью» во Flash. Кроме аспектов использования нового механизма, на этапе реализации сервиса мы познакомимся с достаточно оригинальным подходом работы с «удаленными» объектами во Flash.
Итак, во Flash, начиная с шестой версии, появился объект LocalConnection. Данный объект позволяет передавать сообщения между разными Flash-плеерами. Например, на одной странице есть меню разделов сайта – просто кнопки. На другой части страницы есть уже другой Flash-элемент, задача которого дать дополнительную, графическую информацию для пользователя, раскрыть суть разделов сайта. Пользователь навел на кнопку меню, а другой Flash-элемент показывает новую картинку. До появления объекта LocalConnection такой подход можно было бы реализовать, используя связь через JavaScript на HTML странице, что, как показала практика, очень ненадежно. Используя локальные сети между отдельными Flash файлами, мы избавляемся от лишней работы, и наши приложения наделяются новыми возможностями и работают более устойчиво.
Но возможности работы с локальными сетями куда шире, чем кажется на первый взгляд.
В первую очередь, локальные сети дают нам возможность организовать распределенные вычисления во Flash. Что такое распределенные вычисления? Как уже писалось в главе «Потоки исполнения, механизм конвейера», во Flash имеется только один поток исполнения команд. Там же отмечалось, что работа кода во Flash, если можно так выразиться, фрейм-зависимая. Это значит, что мы не можем параллельно исполнять несколько задач одновременно, и работа кода не должна конфликтовать с процессом перерисовки кадров. Распределенные вычисления позволяют нам создать такую архитектуру Flash-приложений, при которой мы можем вынести ресурсоемкие задачи, в которых активно работает код в один Flash-файл, а графическое отображение будет возложено на другой Flash-плеер. Мы можем пойти еще дальше, и разделить систему на три части – бизнес-логика, работа с данными (например, работа с сервером), работа с пользователем (графика и пользовательский интерфейс). Старая добрая MVC ( model - view - controller ). При реализации такой архитектуры мы получаем три независимо работающих Flash-виртуальных машины – три независимых потока исполнения, которые никак не связаны между собой, и не мешают друг другу.
На практике, мы будем иметь три HTML -тэга на странице, которые загрузят три отдельных swf -файла. Пользователь будет видеть только один, два же других будут «невидимками» (просто максимально небольшого размера и цвета фона страницы). И очевидно, что совместная работа между этими элементами будет реализована посредством передачи данных через LocalConnection .
Однако, если использовать объект LocalConnection в «голом» виде, то организация такой сети будет излишне для нас трудоемка. Во-первых, процесс создания такой сети предполагает некую стандартную процедуру, которую мы будем повторять в каждом проекте, и которую нужно будет автоматизировать. Во-вторых, хотелось бы реализовать некие дополнительные возможности, которые сделали бы работу с сетью более комфортной.
Вернемся к термину «распределенные вычисления». Если краем глаза взглянуть что творится за пределами Flash, то можно убедится в том, что распределенные вычисления стали неотделимой частью современного подхода построения как серверных, так и клиентских приложений. Java RMI (remote method invocation – удаленный вызов методов), CORBA, Microsoft COM. Распределенные вычисления позволяют строить предложения из компонентов, причем так, что компоненты приложений работают максимально независимо друг от друга. Распределенные вычисления позволяют оптимально распределить нагрузку между компонентами приложений, строить серверные приложения на основе кластеров, связывать разные приложения и технологии между собой и так далее. На мой взгляд, к Flash ближе всего Java, поэтому попробуем кое-что перенять из Java. Точнее из Java RMI.
Суть подхода в RMI в том что, во-первых, данные передаются в виде объектов, что удобно. Не нужно изобретать специальный формат данных и протокол передачи данных. Во-вторых, и что важнее для нас, работа с удаленной виртуальной машиной, удаленным классом, производится через специальный объект – «заглушку». Если все упростить, то для того чтобы обратится к некому объекту на «сервере», «клиент» получает доступ к объекту, который имеет тот же набор методов, как и объект на сервере. Обращаясь к методу такого объекта у себя «локально», он «напрямую» работает с удаленным объектом на сервере. В реальности, конечно, он работает с пустой заглушкой, однако механизм получает запрос к методу заглушки и передает его по сети серверу. Сервер же получает запрос, механизм RMI находит запрашиваемый объект и вызывает нужный метод.
Удобство такого подхода в том, что мы у себя локально имеем объект, который является как бы «ссылкой» на удаленный сервер. Вызывая методы у этого объекта, мы работаем «напрямую» с сервером. Нам не нужно каждый раз посылать запрос по некоему адресу («на деревню дедушке»), нет, вот он объект, и если я хочу с ним что-то сделать, я просто вызываю у него то, что нужно.
Отношение клиент-сервер здесь условно. Клиент и сервер могут «меняться местами» и уже бывший сервер будет выступать в роли клиента, и посылать запрос серверу.
Кое-что нужно упростить. В Java мы изначально описываем те методы, которые можно вызывать удаленно. Описывается специальный интерфейс. Мы не можем вызвать метод, которого нет в интерфейсе. Во Flash мы сделаем более гибко. Пусть заглушка принимает любые запросы. Ведь запрос она может получить только от файлов текущего домена. И хакер нам здесь не угроза. Например, из одного файла я обращусь к другому так:
// получить "ссылку" к другому файлу
var o = LocalNet.getNetPlace("any.swf");
// изменить даднные отображаемые в таблице
o._root.dataGrid.setDataProvider(anyArray);
// задать размер таблицы
o._root.dataGrid.setSize(200, 300);
Как видно из кода, мы не просто вызываем методы, мы уже «внутри» другого swf , указываем путь к объекту и у него вызываем методы.
Очевидно, что, используя такой подход, работа с удаленным файлом становится значительно проще и понятнее. Во всяком случае, если все будет так, то построение приложения из трех отдельных частей не кажется такой уж сложной задачей. А это будет именно так.
Уже на этом этапе будущий механизм наделен огромной функциональностью. Что можно еще добавить? Так как мы будем работать сразу с несколькими элементами сети, то было бы правильным реализовать механизм передачи всем участникам сети. Для этого нам нужно будет реализовать два подхода: во-первых, нужно создать механизмы получения списка участников (проще всего получить список адресов элементов зарегистрированных в сети), и во-вторых, нужно сделать механизм «сетевой рассылки», когда одно и то же сообщение рассылается сразу всем.
Нужно предполагать такой вариант событий, когда, по разным причинам, сообщение не было доставлено. Следовательно, нам нужно создать механизм обработки ошибок.
И последнее требование. В примере показывалось, что доступ к файлу мы получаем, используя имя самого файла получателя. Имя файла является его адресом в сети. Такой подход упрощает доступ к адресату, и в большинстве случаев он самый удобный. Однако, нам нужно будет задать адрес напрямую, отличный от адреса по умолчанию. Плюс было бы неплохо, чтобы элемент в сети мог иметь несколько адресов.
Пожалуй хватит. Пора приниматься за реализацию.
Реализация
Начнем с реализации объекта «заглушки». Суть работы заглушки в том, чтобы получить некий запрос и отослать его удаленному участнику сети.
Сначала посмотрим, что необходимо, чтобы запрос обработать «на другом конце провода». Нам нужен некий обработчик получения запроса, который будет иметь следующий вид:
private function onCommand(commandObj)
{
var path = commandObj.path;
var method = commandObj.method;
var args = commandObj.args;
var obj = eval(path.join("."));
obj[method].apply(obj, args);
}
Обработчик должен получить объект, который содержит три поля: путь к объекту в виде массива шагов, имя метода, и массив аргументов. Имея эти три элемента, мы легко вызовем необходимый нам метод у нужного объекта и передадим ему его параметры.
Эти три элемента, полученные сервером, должен получить объект-заглушка. Так как в данном объекте нет никаких запрашиваемых полей и методов, мы реализуем работу по сбору статистики с помощью метода __resolve . Суть работы данного встроенного в AS метода в том, чтобы обрабатывать запросы к несуществующим полям. В случае запроса к несуществующему полю объекта, он получит аргумент - имя поля в виде строки. А, вернув некое значение, метод как бы делает несуществующее поле реально существующим для внешнего наблюдателя. Например:
var o = new Object();
o.__resolve = function(badParam)
{
trace("param non defined: " +
badParam);
return "any string";
}
trace(o.testParam);
// output:
// param non defined: testParam
// any string
Наделив наш объект-заглушку таким методом, мы легко сможем собрать статистику в виде пути к объекту, возвращая, при каждом шаге-запросе, ссылку на самого себя:
var o = new Object();
o.paths_array = new Array();
o.__resolve = function(step)
{
this.paths_array.push(step);
return this;
}
o.step_one.step_two;
trace(o.path_array);
// output: step_one, step_two
Но кроме пути нам нужно узнать имя метода и параметры, которые передаются данному методу. И здесь мы применим красивое, можно даже сказать «изобретательское» решение. Наш объект-заглушка будет функцией. В конце маршрута наш объект будет вызван как функция и ему будет переданы аргументы:
Согласитесь, что решение действительно красивое, и достойно к применению в дальнейшем.
Осталось привести в порядок метод создания заглушки:
private function createResolver(
pathToLocal:String):Function
{
var f = function()
{
var scope =
arguments.callee.__scope;
var method =
scope.path.pop();
var path = scope.path;
var args = arguments;
var pathToLocal =
scope.pathToLocal;
scope.path = [];
scope.client.sendCommand(
pathToLocal,
path,
method,
args);
}
var s = f.__scope = {};
s.client = this;
s.path = [];
s.pathToLocal = pathToLocal;
f.__resolve = function(paramName)
{
this.__scope.path.push(paramName)
return this;
}
return f;
}
Несколько улучшений.
Во-первых, создан специальный объект, который хранит данные по сбору статистики запроса с префиксом из двух подчерков (__scope ). Это избавляет нас от проблемы конфликта имен в удаленном объекте. Два подчерка, по негласному правилу означают, что поле приватное. А приватные методы в удаленном объекте мы вызывать, конечно же, не будем.
Во-вторых, при успешном окончании сбора статистики запроса, мы передаем полученные данные в метод для отправки сообщения ( sendCommand ). И, в-третьих, обнуляем статистику в конце работы, что позволяет нам пользоваться одним и тем же объектом-заглушкой неограниченное количество раз.
Теперь напишем метод getNetPlace , который мы использовали в первом «реальном» примере использования сети. Метод getNetPlace должен возвращать объект для доступа к элементу сети:
public static function getNetPlace(
netPath:String):Object
{
return __instance.createResolver(netPath);
}
Метод статический, и обращается он к экземпляру класса LocalNet , который хранится в статическом поле __instance (паттерн «Singleton»). У экземпляра вызывается метод по созданию заглушки.
Данный паттерн запрещает создавать экземпляры объекта – конструктор приватный. Работа с классом производится только через статические методы. Почему так сделано? Во-первых, для того чтобы начать работу с классом не нужно делать усилия по созданию его экземпляра. Во-вторых, нам не нужно будет помнить о том, что класс уже создан, и создание второй сети может привести к сбоям. В-третьих, мы еще сильнее инкапсулировали логику работы класса, обеспечивая доступ к методам класса не напрямую, а через статические методы.
Остальная часть реализации локальной сети достаточно банальна, и не стоит выкладывать в эту статью полный его листинг.
Ниже представлен простой пример иллюстрирующий работу локальной сети.
Два отдельных swf -файла с минимальной функциональностью. Двигаем мышкой над одним из них. Шарик в двух файлах двигается синхронно.
import com.potapenko.local.LocalNet;
LocalNet.setAddress("first")
function moveBoll(x, y) {
_root.a_mc._x = x;
_root.a_mc._y = y;
}
var o = LocalNet.getNetPlace("second");
this.onMouseMove = function ()
{
// to second
o._root.moveBoll(this._xmouse,
this._ymouse);
// _root
_root.moveBoll(this._xmouse,
this._ymouse);
}
import com.potapenko.local.LocalNet;
LocalNet.setAddress("second")
function moveBoll(x, y) {
_root.a_mc._x = x;
_root.a_mc._y = y;
}
var o = LocalNet.getNetPlace("first");
this.onMouseMove = function ()
{
// to first
o._root.moveBoll(this._xmouse,
this._ymouse);
// _root
_root.moveBoll(this._xmouse,
this._ymouse);
}
Заключение
Пример наглядно иллюстрирует, что работать с сетью во Flash стало как никогда просто.
Вот такой он простой класс, LocalNet , но каковы перспективы…