Россия, Звенигород |
Объекты XPCOM
16.2.6. Прерывания и сигналы
Не существует способа отправлять или перехватывать сигналы операционной системы из скриптов JavaScript. Чтобы компонент XPCOM мог перехватывать сигналы, он должен быть написан на Java или C/C++.
Интерфейс nsIThread может использоваться для управления выполнением фрагмента кода, которое может быть прервано. При этом код, выполнение которого должно быть прервано, не может быть написан на JavaScript. Интерпретатор JavaScript платформы Mozilla выполняется в одном потоке вычислений (thread), и не может прервать собственное выполнение. Из этого следует, что прерывания, основанные на потоках вычислений, неприменимы в приложениях, написанных исключительно на JavaScript.
В качестве замены прерываний могут использоваться технологии, ориентированные на события (см. "События" "События"), и система команд Mozilla (см. "Команды" "Команды").
16.2.7. Сетевые протоколы
Mozilla поддерживает ряд хорошо известных прикладных сетевых протоколов, например FTP. При этом Mozilla предполагает, что протоколом транспортного уровня является TCP/IP. Другие транспортные протоколы, например RS232, X.25 или TP4, могут использоваться, только если они "упакованы" в TCP/IP. Mozilla поддерживает следующие протоколы низкого уровня:
- TCP/IP версий 4 и 6. В версиях Mozilla, собранных с параметрами по умолчанию, поддержка TCP/IP версии 6 отключена. Чтобы активизировать ее, необходимо при сборке использовать параметр --enable-ipv6.
- DNS. Платформа поддерживает многопоточные (параллельные) обращения к DNS.
- FTP. Mozilla поддерживает протокол FTP, однако Менеджер загрузок не поддерживает возобновления загрузки, по крайней мере, в версиях до 1.4 включительно.
- RPC (удаленный вызов процедуры). Mozilla поддерживает RPC c использованием XML, но не RPC, основанный на NDR/XDR. Именно последний метод является традиционным способом выполнения RPC.
- SSL (протокол защищенных сокетов) и SOCKS. Mozilla поддерживает протокол SSL версий 2 и3, а также протокол SOCKS версий 4.0 и 5.0. SSL используется для поддержки протоколов Secure SMTP (SMIME) и Secure HTTP (HTTPS).
Как правило, приложения на платформе Mozilla не работают непосредственно с сетевыми протоколами. Сетевые ресурсы идентифицируются при помощи URL, и префикс метода доступа (например, http:), входящий в состав URL, определяет необходимый протокол. Как правило, объект сетевого канала принимает URL, после чего поддержка нужного протокола задействуется платформой автоматически, и с точки зрения приложения все "просто работает". Тем не менее, конкретные протоколы доступны в виде объектов, которые могут быть созданы при помощи следующей пары XPCOM:
@mozilla.org/network/protocol;1?name={x} nsIProtocolHandler
В приведенном имени компонента { x } должно быть заменено на идентификатор конкретного протокола, например ftp или http. Все протоколы (точнее, схемы URL), поддерживаемые Mozilla, доступны как содержимое массива window.Components.classes. Каждый из них представлен отдельным компонентом.
С помощью настроек можно сконфигурировать Mozilla на уровне портов IP, активизируя (открывая) или отключая (закрывая) конкретные порты. Открытие порта имеет практический смысл лишь в том случае, когда соответствующий порт открыт на уровне операционной системы. Открытие дополнительных портов снижает уровень защищенности системы на уровне приложений, и может быть рекомендовано лишь при использовании сетевого экрана (файрволла). Получить доступ к полному набору сетевых настроек Mozilla можно, введя в строке адреса браузера about:config. Имена параметров, имеющих отношение к сети, начинаются с префикса network.
Разработчики приложений также имеют доступ к сокетам. Операционные системы представляют соединение TCP/IP при помощи сокета, имеющего дескриптор, аналогичный дескриптору файла. На платформе Mozilla соединение, дескриптор и прочие особенности реализации инкапсулированы в объект, с которым и имеют дело скрипты. Такой объект-сокет является самым низким уровнем работы с сетью, с которым может иметь дело разработчик на платформе, собранной с параметрами по умолчанию.
Наконец, существует проект Protozilla, информация о котором доступна на сайте http://www.mozdev.org, и который позволяет расширять поддержку сетевых протоколов в Mozilla. С помощью расширения, разработанного в рамках этого проекта, можно добавлять к Mozilla поддержку новых протоколов, причем для этого достаточно программирования только на JavaScript. Требования к этим протоколам следующие: они должны быть реализованы поверх сокетов TCP/IP, терпимы к небольшим задержкам, соответствующий код должен реализовывать интерфейс nsIProtocolHandler и быть зарегистрирован как полноценный компонент XPCOM.
Теперь мы переходим к обсуждению конкретных задач, возникающих при работе с сетью на низком уровне. Работа с сетью на уровне приложений описана в разделах "Передача данных" и "Web-скрипты".
16.2.7.1. Определение IP-адреса
Чтобы определить IP-адрес по заданному доменному имени, используйте следующую пару XPCOM:
@mozilla.org/network/dns-service;1 interface nsIDNSService
Объект, созданный таким образом, возвращает IP-адрес для заданного имени домена или текущего узла в форме строки вида "192.168.1.10". Разрешение доменных имен – медленная операция. Xтобы работа приложения не приостанавливалась до завершения, следует использовать метод lookup(), которому должен быть передан слушатель с интерфейсом nsIDNSListener. В этом случае запрос будет выполняться асинхронно. Реализуйте объект-слушатель на чистом JavaScript.
16.2.7.2. Создание сокета
Создание соединения с использованием сокета включает несколько этапов.
Для работы с сокетом вам, в конечном счете, понадобится создать объект nsITransport. Получив этот объект, можно до некоторой степени забыть, что вы работаете с сокетом, и использовать методы более высокого уровня, описанные в разделе "Передача данных". В целом, техника работы с сокетом, доступная разработчику приложений на платформе Mozilla, отличается довольно высоким уровнем абстракции. Например, ему недоступен API ioctl(2) для настройки параметров сокета.
Создавая объект nsITransport, необходимо предусмотреть возможность того, что между платформой Mozilla и удаленным компьютером, с которым устанавливается соединение, находится proxy-сервер. Если вы не уверены в его отсутствии, создайте объект nsIProxyInfo для адреса удаленного компьютера. Объект nsIProxyInfo может быть создан при помощи методов newProxyInfo() или examineForProxy() следующей пары XPCOM:
@mozilla.org/network/protocol-proxy-service; nsIProtocolProxyService
Затем, используя полученный объект nsIProxyInfo или null, если вы уверены в том, что proxy-сервера не существует, создайте объект-фабрику для получения объекта nsITransport. Объект-фабрика создается при помощи следующей пары XPCOM:
@mozilla.org/network/socket-transport-service;1 nsISocketTransportService
Затем нужно создать объект nsITransport, передав объект nsIProxyInfo методу createTransport() объекта-фабрики. Полученный объект будет поддерживать интерфейс nsISocketTransport, представляющий простой сокет TCP/IP. Если необходимо создать сокет SOCKS, следует использовать метод createTransportOfType() и указать в качестве типа "socks" для протокола SOCKS 5.0 или "socks4" для протокола SOCKS 4.0. Чтобы создать сокет UNIX (IPC), укажите тип "ipc". Транспортный объект, созданный таким образом, может использоваться как сокет или как обычный транспортный объект.
Сокеты SOCKS используют шифрование, поэтому их можно создать лишь в том случае, когда в составе платформы установлены и настроены соответствующие модули шифрования и ключи. По умолчанию эти элементы установлены и сконфигурированы в профиле пользователя как классического браузера, так и браузера Mozilla.
В состав платформы Mozilla входят и другие интерфейсы для работы с сокетами, однако все они недоступны из JavaScript. Просматривая определения интерфейсов в файлах XPIDL, обращайте внимание на пометку [noscript] перед именем интерфейса. Она означает, что интерфейс недоступен из JavaScript.
В листинге 16.4 приведена простая программа на Perl, которая может использоваться в качестве сервера для тестирования соединений, установленных через сокет. Эта программа принимает данные от всех клиентов, подключившихся к ней, и направляет их в стандартный поток вывода stdout. Программа не поддерживает протокол SOCKS и не возвращает клиентам никаких данных.
use IO::Socket; my ($server, $client, $host); $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => 80, Listen => SOMAXCONN, Reuse=> 1); while ($server && ($client = $server->accept())) { while ( <$client> ) { print; } close $client; }Листинг 16.4. Реализация сервера для тестирования соединений
Для работы этой программы необходима корректная настройка порта на уровне операционной системы.
16.2.7.3. Создание сеанса FTP
Платформа Mozilla не поддерживает работу с сеансами FTP на низком уровне. Элементарную операцию, доступную для разработчика, составляет обращение к URL при помощи объекта nsIChannel. Это означает, что каждый сеанс FTP состоит не более чем из четырех команд. На псевдокоде их можно записать следующим образом:
open {hostname and port} //открыть сеанс cd {directory} // перейти в нужный каталог dir OR get {file} //получить содержание каталога или нужный файл close //закрыть сеанс
Этот сеанс FTP осуществляется внутри платформы. Разработчик приложений не получает информации о выполнении отдельных команд и не может отдавать собственные команды. На практике это означает, что единственный способ создания сеанса FTP, доступный разработчику приложений, - запросить документ, в URL которого указан метод доступа ftp:. Эта процедура подробно описана в разделах "Загрузка файлов" и "Каналы".
Если приложению необходимо перемещаться по иерархии каталогов FTP, потребуется несколько последовательных запросов. Как известно, URL может представлять не только отдельный файл, но и каталог FTP. При обращении к такому URL платформа возвращает содержимое каталога, правда, оформленное в виде HTML-документа. Проанализировав этот документ, можно получить список файлов и подкаталогов, находящихся в исходном каталоге, к которым, в свою очередь, можно сформировать запрос.
Тот же самый подход – сессия FTP, выглядящая как обращение к URL, – используется и при загрузке файлов на FTP-сервер. Подробнее об этом рассказано в разделе "Загрузка файлов".
Если ни один из предложенных методов не подходит для ваших целей, можно создать два сокета средствами JavaScript и, используя их, самостоятельно реализовать протокол FTP. При этом важно позаботиться о производительности приложения. Можно ожидать, что этот подход окажется почти столь же трудоемким, как и написание полноценного компонента XPCOM, реализующего протокол FTP, на C/C++.
16.2.8. Процессы и потоки вычислений
Возможно, фрагмент кода, который предполагается запустить из основной программы, не нуждается в отдельном процессе или потоке вычислений (thread). В таком случае можно запланировать его выполнение при помощи очереди событий. О том, как это сделать, рассказано в "События" "События". Если создать процесс или поток все же необходимо, читайте этот раздел.
Простейший способ запустить отдельную программу – активизировать ее с помощью командной оболочки операционной системы. При этом программа запускается так, как если бы ее активизировал пользователь (например, щелкнув по значку на рабочем столе). Чтобы это сделать, сначала нужно создать файловый объект при помощи следующей пары XPCOM:
@mozilla.org/file/local;1 nsILocalFile
Затем следует связать полученный объект с каким-либо существующим файлом (см. раздел "Файлы и папки"), после чего вызвать метод launch() этого объекта. Имейте в виду, что в системе UNIX поведение метода launch() определяется настройками среды GNOME, а не значением переменной окружения PATH. Приложение, запущенное таким образом, не зависит от процесса, в котором выполняется платформа Mozilla, и не может быть остановлено средствами последней.
Более общий способ запуска процессов связан с использованием следующей пары XPCOM:
@mozilla.org/process/util;1 nsIProcess
Имейте в виду, что этот интерфейс до сих пор не реализован полностью на всех платформах, поддерживаемых Mozilla. Чтобы воспользоваться им, как и в предыдущем случае, нужно создать объект nsILocalFile и связать его с соответствующим исполняемым файлом. Поскольку код для работы с процессами, как правило, зависит от платформы, можно использовать для этого непереносимый метод initWithPath(). Передайте полученный объект методу init() объекта nsIProcess, а затем вызовите метод run(), чтобы создать процесс. Пример вызова этого метода приведен ниже:
var blocking = true; var argv = ["arg1","arg2"]; var result = {}; nsIProcess_object.run(blocking, argv, argv.length, result);
В процессе выполнения метода run() к объекту result, который является обязательным аргументом метода, добавляется поле value, которому в свою очередь присваивается значение 0 в случае успешного запуска процесса. Если аргумент blocking имеет значение true, выполнение Mozilla будет приостановлено до завершения запущенного процесса; при этом никакие окна Mozilla обновляться не будут. Если же аргументу присвоено значение false, выполнение Mozilla будет продолжено. В любом случае, по завершении запущенного процесса будет установлено значение свойства exitValue объекта nsIProcess. Придется поэкспериментировать, чтобы установить, какие значения соответствуют нормальному завершению процесса на различных платформах.
Работа с потоками вычислений более сложна. С точки зрения разработчика приложений, отдельный поток вычислений представляет собой всего лишь фрагмент кода, выполнение которого запланировано при помощи метода window.setTimeout(). Строго говоря, в данном случае существует лишь иллюзия отдельного потока – запланированное выполнение скрипта ни при каких условиях не начнется раньше, чем завершится текущий скрипт.
Это связано со способом реализации интерпретатора JavaScript в составе платформы Mozilla. На низком уровне платформа поддерживает отдельные потоки вычислений. Система потоков компонентной модели XPCOM может рассматриваться как упрощенный аналог системы потоков Microsoft COM. Потоки вычислений используются для разнообразных целей, простейший пример – работа с FTP, для которой требуется одновременная поддержка двух соединений. Однако интерпретатор JavaScript, входящий в состав платформы, выполняется лишь в одном потоке. Хотя интерпретатор сам по себе допускает наличие нескольких одновременно работающих экземпляров, платформа Mozilla не использует эту возможность.
Хотя интерпретатор не поддерживает истинные потоки вычислений, для работы с потоками предусмотрен ряд интерфейсов. Фактически, они позволяют организовывать код более аккуратно, чем при использовании методов setTimeout() и setInterval(). Последовательность действий по созданию потока приведена в листинге 16.5:
var Cct = Components.classes["@mozilla.org/thread;1"]; var Cit = Components.interfaces.nsIThread; var thread = { Run : function () { alert(this.foo+" – выполняемый поток"); } foo : "bar" }; var mgr = Cct.createInstance(Cit); mgr.init(thread, 0, Cit.PRIORITY_NORMAL, Cit.SCOPE_GLOBAL, Cit.STATE_JOINABLE); mgr.join(); alert("поток создан");Листинг 16.5. Пример создания потока вычислений
Объект, представляющий фрагмент исполняемого кода (в данном случае – объект thread ), поддерживает интерфейс nsIRunnable. Он содержит собственно исполняемый код (метод Run() ), а также данные, которые могут потребоваться для выполнения этого кода. Объект mgr (менеджер потока) содержит данные о конфигурации и состоянии потока вычислений. С помощью вызова метода join() поток помещается в очередь на выполнение или возобновление приостановленного выполнения (приостановка и последующее возобновление выполнения невозможны для кода, написанного для JavaScript). join() не эквивалентен методу eval(), поскольку его вызов не приводит к немедленному выполнению кода. Вместо этого код, представленный объектом, помещается в очередь, и интерпретатор JavaScript дойдет до него не раньше, чем закончится выполнение текущего скрипта. Поскольку интерпретатор выполняется в одном потоке и не может быть приостановлен другим потоком, сообщение в последней строке листинга всегда выдается раньше, чем сообщение из кода в объекте thread.
Это означает, что ситуация конкуренции потоков (race condition) в JavaScript на платформе Mozilla невозможна – один "поток вычислений" должен завершиться до запуска следующего "потока". Поэтому, например, в скриптах JavaScript невозможно написать бесконечный цикл, рассчитанный на прерывание из другого потока, – такой цикл никогда не будет прерван. Таким образом, "потоки вычислений" в JavaScript являются всего лишь средством структурирования кода, но не параллельного выполнения. Работа с истинными потоками выполнения из скриптов возможна лишь при обращении к компонентам XPCOM, которые написаны не на JavaScript и поддерживают интерфейс nsIRunnable.
Кроме того, скрипт JavaScript может создавать истинные потоки вычислений, взаимодействуя с виртуальной машиной Java. При этом создаются "чистые" потоки Java, которые выполняются в соответствующей программной среде.