Библиотека сайта rus-linux.net
Twisted
Глава 21 из книги "Архитектура приложений с открытым исходным кодом", том 2.Оригинал: Twisted
Автор: Jessica McKellar
Перевод: А. Панин
Объекты Deferred
Объект Deferred
является абстракцией над результатом, которого на данный момент не существует. Он также облегчает управление цепочками функций обратного вызова, используемыми для получения этого результата. При возврате из функции объект Deferred
выступает в роли обещания того, что функция вернет результат на определенном шаге. Этот возвращенный объект Deferred
содержит ссылки на все функции обратного вызова, зарегистрированные для данного события, поэтому только этот единственный объект должен передаваться между функциями и гораздо проще работать с ним, а не управлять отдельными функциями обратного вызова.
Объектам Deferred
соответствуют пары цепочек функций обратных вызовов, одна для обработки успешных операций (с помощью функций обратного вызова для обработки данных) и одна для обработки ошибок (с помощью специальных функций обратного вызова для обработки ошибок). Объекты Deferred
создаются с двумя изначально пустыми цепочками. После этого добавляются пары функций обратного вызова для обработки данных и ошибок, которые будут использоваться для обработки удачных и неудачных операций в каждой точке процесса обработки событий. В тот момент, когда осуществляется асинхронная доставка результата выполнения операции, "активизируется" объект Deferred
и вызываются соответствующие функции обратного вызова для обработки данных и событий в том порядке, в котором они были добавлены.
Deferred
версия асинхронной программы для получения страницы на основе URL в псевдокоде:
from twisted.internet import reactor import getPage def processPage(page): print page def logError(error): print error def finishProcessing(value): print "Завершение работы..." reactor.stop() url = "http://google.com" deferred = getPage(url) # Функция getPage возвращает объект Deferred deferred.addCallbacks(success, failure) deferred.addBoth(stop) reactor.run()
В этой версии вызываются те же обработчики событий, но все они регистрируются с помощью одного объекта Deferred
вместо распределения по коду и передачи имен функций в качестве аргументов функции getPage
.
Объект Deferred
создается с двумя уровнями функций обратного вызова. Во-первых, функция addCallbacks
добавляет функцию обработки данных processPage
и функцию обработки ошибок logError
на первый уровень их соответствующих цепочек. После этого с помощью функции addBoth
функция обратного вызова finishProcessing
добавляется на второй уровень обеих цепочек.
Цепочки функций обратного вызова в форме диаграммы выглядят примерно так, как показано на Рисунке 21.1.
Рисунок 21.1: Цепочки функций обратного вызова
Объекты Deferred
могут быть активизированы только однократно; повторная попытка их активизации приведет к возникновению исключения (Exception
). Это позволяет добиться семантики объектов Deferred
, аналогичной семантике блоков try/except
в синхронных версиях, которые упрощают понимание процесса обработки асинхронных событий и позволяют избежать коварных ошибок, вызванных тем, что функции обратного вызова вызываются больше одного раза для события.
Понимание принципов использования объектов Deferred
является важным для понимания принципа работы программ на основе Twisted. Однако, при использовании высокоуровневых абстракций, предоставляемых фреймворком Twisted для сетевых протоколов, обычно вообще не приходится использовать объекты Deferred
напрямую.
Абстракция Deferred
является достаточно мощной и была заимствована другими управляемыми событиями платформами, в числе которых jQuery, Dojo и Mochkit.
Транспорты
Транспорты представляют соединение между двумя конечными точками, взаимодействующими посредством сети. Транспорты отвечают за описание параметров соединения, таких, как является ли соединение потоко- или дейтаграммно-ориентированным, какие алгоритмы применяются для контроля потока, а также какова надежность соединения. TCP-, UDP- и Unix-сокеты являются примерами транспортов. Они спроектированы так, чтобы быть "минимально функциональными примитивами, которые могут быть максимально используемыми повторно" и отделены от реализаций протоколов, позволяя множеству протоколов использовать один и тот же тип транспорта. Транспорты реализуют интерфейс ITransport
, который имеет следующие методы:
write |
Записать последовательно какие-либо данные в физическое соединение без блокировок. |
writeSequence |
Записать список строк в физическое соединение. |
loseConnection |
Записать все ожидающие данные, после чего закрыть соединение. |
getPeer |
Получить удаленный адрес данного соединения. |
getHost |
Получить локальный адрес данного соединения. |
Отделение транспортов от протоколов также упрощает тестирование двух уровней. Исследуемый транспорт может просто записать данные в строку для проверки.
Протоколы
Протоколы устанавливают методы асинхронной обработки событий сети, HTTP, DNS и IMAP являются примерами прикладных сетевых протоколов. Протоколы реализуют интерфейс IProtocol
, который имеет следующие методы:
makeConnection |
Создать соединение для транспорта с сервером. |
connectionMode |
Вызывается тогда, когда соединение создано. |
dataReceived |
Вызывается тогда, когда приняты данные. |
connectionLost |
Вызывается при закрытии соединения. |
from twisted.internet import protocol, reactor class Echo(protocol.Protocol): def dataReceived(self, data): # Любые данные после приема должны быть отправлены назад self.transport.write(data) class EchoFactory(protocol.Factory): def buildProtocol(self, addr): return Echo() reactor.listenTCP(8000, EchoFactory()) reactor.run()
from twisted.internet import reactor, protocol class EchoClient(protocol.Protocol): def connectionMade(self): self.transport.write("hello, world!") def dataReceived(self, data): print "Ответ сервера:", data self.transport.loseConnection() def connectionLost(self, reason): print "соединение разорвано" class EchoFactory(protocol.ClientFactory): def buildProtocol(self, addr): return EchoClient() def clientConnectionFailed(self, connector, reason): print "Не удалось осуществить соединение - всего доброго!" reactor.stop() def clientConnectionLost(self, connector, reason): print "Соединение разорвано - всего доброго!" reactor.stop() reactor.connectTCP("localhost", 8000, EchoFactory()) reactor.run()
В ходе выполнения сценария сервера должен начать работу TCP-сервер, ожидающий соединений на порту 8000. Сервер использует протокол Echo
и данные передаются с помощью транспорта TCP. В ходе выполнения сценария клиента с сервером организуется соединение по протоколу TCP, после чего серверу передаются данные, которые он присылает назад, соединение закрывается, а цикл обработки событий завершает свою работу. Фабрики протоколов (EchoFactory) используются для создания экземпляров классов протоколов на двух сторонах соединения. С двух сторон соединения производится асинхронный обмен данными; метод connectTCP
осуществляет регистрацию функций обратного вызова в цикле обработки событий для получения уведомлений о том, что данные доступны для чтения из сокета.
Продолжение статьи: Приложения