Наши партнеры

UnixForum





Библиотека сайта 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 осуществляет регистрацию функций обратного вызова в цикле обработки событий для получения уведомлений о том, что данные доступны для чтения из сокета.


Продолжение статьи: Приложения