ГЛАВНАЯ      ДОКУМЕНТАЦИЯ      СТАТЬИ      ПРОГРАММЫ      ССЫЛКИ      ФОРУМ      ДРУГОЕ   

Межпроцессная диспетчеризация событий в web-приложениях Python, часть 2

В первой статье цикла о межпроцессной диспетчеризации событий в Python я описал ситуацию с долгоживущими процессами веб-приложения. При необходимости поддержки данных состояния приложения в такой системе нам нужен механизм, позволяющий передавать события, возникшие в одном из процессов другим процессам, подобно тому как события передаются внутри процесса. В этом механизме также будет использоваться PyDispatcher, как средство генерирования и диспетчеризации событий внутри всех процессов, участвующих в обмене.

Общая архитектура

  1. Событие, как обычно, генерируется вызовом dispatcher.send модуля PyDispatcher.
  2. Модуль, отвечающий за межпроцессную диспетчеризацию, подписывается на все события, используя dispatcher.connect (подписка на все события производится, если при вызове dispatcher.connect опустить второй аргумент — объект-событие.)
  3. При возникновении события модуль межпроцессной диспетчеризации использует какой-либо транспорт, основанный на механизме межпроцессного взаимодействия (IPC), который гарантирует доставку события другим процессам приложения. При этом
    • данные, связанные с событием (сам объект-событие, отправитель и аргументы, переданные вместе с событием) сериализуются;
    • сериализованные данные передаются механизму IPC, реализующему транспорт, возможно с указанием возможных получателей (процессов приложения).
  4. Все процессы приложения организуют подключение к тому же транспорту, через который осуществляется отправка событий. Этим занимается тот же модуль межпроцессной диспетчеризации. Т.е. он работает как на отправку, так и на прием событий.
  5. При получении через транспорт сообщения с сериализованными данными события, данные десериализуются, полученные значения объекта-события, отправителя и аргументов передаются dispatcher.send, и тем самым превращаются в локальное событие, обрабатываемое диспетчером PyDispatcher.
Это очень общее описание архитектуры. При доработке деталей нам нужно определить наиболее удобный механизм IPC, протокол передачи сообщений, учесть, что одно и то же событие не должно приходить одному и тому же процессу дважды.
Кроме того, необходимо ограничить круг событий, которым требуется межпроцессная диспетчеризация. Далеко не все события следует передавать другим процессам. Нас интересуют только те события, которые сигнализируют об изменении состояния приложения. События, влияющие только на состояние процесса — такие как события инициализации модели в Django — транслировать явно не нужно, более того, это чревато ошибками, которые будут чрезвычайно непросты в отладке.

Реализация

Я напишу краткий код межпроцессного диспетчера событий. Некоторые детали, касающиеся обработки ошибок, конфигурирования и вывода отладочной информации будут опущены для краткости изложения. Ссылка на код более полного, рабочего прототипа будет дана в конце статьи.

Транспорт

Механизмов IPC существует множество. Запись значений в файлы, доступные всем процессам, и чтение данных из этих файлов; использование разделяемой памяти (shm); использование очередей сообщений и семафоров SystemV; использование сокетов UNIX, а также сокетов сетевых протоколов (и основанных на этих протоколах сервисов) — всё это способы, которые могут использоваться для межпроцессного взаимодействия.
Так как мы помним, что процессы нашего приложения могут физически располагаться на нескольких (возможно даже нескольких сотнях или тысячах) разных компьютеров, то нам интересны только те способы, которые могут обеспечить связь между процессами на разных компьютерах. В сухом остатке - наш любимый протокол IP и транспорты, на нем основанные.
Событие должно доставляться всем процессам приложения. Откуда процессу, в котором произошло событие, знать, сколько еще существует процессов-побратимов, и как их всех оповестить? Проще и понятнее — через специальный сервер, который ведет учет всех процессов. Сообщение отправляется серверу, а он уже занимается доставкой. Следовательно, архитектура теперь у нас - клиент-серверная. Клиент и сервер будут использовать общий транспорт для отправки и доставки сообщений. Мы по возможности будем писать код клиента и сервера, независимый от транспорта. Транспорт будет выделен в отдельный модуль, который будет вызываться из кода клиента и сервера, и которому будет передаваться обработчик, который и будет объектом клиента или сервера.
Например, напишем транспорт для протокола UDP. Этот протокол очень простой и быстрый, хотя не лишен недостатков, о чем ниже.
'''module netdispatcher.transport.udp'''
import asyncore, socket, weakref
class DispatchTransport(asyncore.dispatcher):
    def __init__(self, host, port, handler):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.set_reuse_addr()
        self.bind((host, port))
        self.handler = weakref.ref(handler)
   
    def writable(self):
        return False
   
    def handle_read(self):
        try:
            data, client_addr = self.socket.recvfrom(2048)
        except socket.error, why:
            if why[0] != asyncore.EWOULDBLOCK:
                pass
            raise socket.error, why
        else:
            handler = self.handler()
            if handler:
                handler.processMessage(data, client_addr)
            else:
                self.stop()   
            handler = None   
   
    def handle_connect(self):
        pass
   
    def handle_error(self):
        raise
   
    def handle_close(self):
        pass
   
    def sendMessage(self, message, to):
        self.socket.sendto(message, to)
   
    def stop(self):
        self.socket.close()
Мы используем модуль asyncore из стандартной библиотеки, который обеспечивает асинхронную обработку входящих соединений TCP/IP. В нашем случае используется UDP, в котором соединение отсутствует, но этот модуль может использоваться и для UDP. Я не буду долго объяснять, как работает asyncore, об этом можно почитать в документации или это будет темой другой статьи. В данном случае нам интересно только то, что этот транспорт позволяет получать датаграммы UDP и отправлять их по указанному адресу.
При инициализации транспорта в метод __init__ передается аргумент handler — объект-обработчик (клиент или сервер). Но в то же время сам объект клиента или сервера содержит ссылку на объект транспорта. Таким образом мы получаем циклическую ссылку и нарываемся на потенциальную утечку памяти. При попытке удалить объект клиента или сервера мы должны удалить и объект транспорта, но транспорт ссылается на этот же объект обработчика, и будет попытка удалить его. Но т.к. счетчик ссылок ни на один ни на другой объект не обнуляется, они не будут удалены и останутся в памяти. Чтобы избежать этого, мы используем слабую ссылку из транспрота на объект-обработчик при помощи модуля weakref. Такая ссылка не увеличивает счетчик ссылок объекта, и утечки не будет.
При получении датаграммы из сети транспорт вызывает метод processMessage объекта-обработчика, т.е. сам транспорт интерпретацией данных не занимается. Для отправки сообщений объект-обработчик вызывает метод sendMessage объекта-транспорта.

Трансляция и обработка событий

Напишем код сервера для нашего транспорта.
'''module netdispatcher'''
from netdispatcher.transport.udp import DispatchTransport

'''Базовый класс - будет использован и для сервера и для клиента'''
class DispatchBase(object):
    transport = None
    def __init__(self, host, port):
        self.transport = DispatchTransport(host, port, self)
           
    def sendMessage(self, message, address):   
        self.transport.sendMessage(message, address)
       
    def stop(self):   
        self.transport.stop()
        self.transport = None   
   
    def __del__(self):
        self.stop()

class DispatchMasterServer(DispatchBase):
    clients = None
    def __init__(self, host, port):
        super(DispatchMasterServer, self).__init__(host, port)
        self.clients = {}
   
    def processMessage(self, data, client_addr):
        self.registerClient(client_addr)
        self.broadcastMessage(data, skipClient=client_addr)
             
    def registerClient(self, client_addr):   
        key = '%s:%d'%client_addr
        if not self.clients.get(key, None):
            self.clients[key] = client_addr
   
    def broadcastMessage(self, message, skipClient):   
        key = '%s:%d'%skipClient
        for k, address in self.clients.items():
            if k != key:
                self.sendMessage(message, address)
Наш сервер очень прост. При инициализации он устанавливает транспорт. При получении пакета, он добавляет клиента в список клиентов, и переправляет сообщение всем клиентам, которых он успел увидеть, кроме того, который послал сообщение. Для краткости я не усложняю здесь код процедурой регистрации клиента. Клиент, который желает получать сообщения о событиях, просто должен отправить пакет любого содержания серверу.
Код клиента мы рассмотрим чуть позже.
Понятно, что событие должно быть обработано в одном процессе только один раз. Это означает, что
  • Одному процессу одно событие должно быть гарантированно отправлено один раз. Это контролирует сервер.
  • Событие не должно поступать через сеть тому же процессу, в котором оно возникло.
    • С одной стороны, это контролируется на сервере. Сервер не будет отправлять сообщение о событии тому процессу, от которого он получил это сообщение.
    • С другой стороны, процесс, получивший событие через сеть, не должен отправлять его обратно в сеть. Такая ситуация в нашем случае очень вероятна, т.к. событие при получении из сети отправляется диспетчеру PyDispatcher внутри процесса. Тот, в свою очередь, диспетчеризует это событие, и оно приходит обратно нашему модулю трансляции событий (как мы помним, он подписан на все события в процессе, и он его получит). Следовательно, необходимо как-то помечать событие, пришедшее через сеть, и не транслировать помеченное событие чтобы круг не замкнулся.

Ограничения транслируемых событий

Важно понимать, что мы можем транслировать. Перечислю данные, связанные с событием:
  • Сам объект-событие
  • Отправитель. Обычно в качестве отправителя выступает объект или функция, которая вызвала событие. Однако механизм PyDispatcher не требует обязательного указания отправителя, а также не присваивает по умолчанию в качестве отправителя объект, в котором событие произошло. В Django, как правило, в качестве отправителя указывается класс объекта, который вызвал событие, и в этом есть свой резон. Более того, в случае с сетевой диспетчеризацией такое правило имеет большое значение, об этом будет сказано ниже.
  • Аргументы, переданные вместе с событием. Могут быть любыми объектами, однако в случае сетевой диспетчеризации на характер объектов нам нужно наложить ограничения, об этом также ниже.
Поговорим о передаче объектов Python между процессами. Как я уже писал, объекты перед передачей должны быть сериализованы в строковое представление, из которого потом будет возможность их восстановить. Основная проблема при сериализации и восстановлении в нашем случае в том, что восстановленный объект хотя и будет идентичен по содержанию сериализованному объекту, это все же будет другой объект. Он будет независим от сериализованного объекта — будет находиться в другой области памяти даже в случае когда восстановлен в том же процессе, а в нашем случае — вообще будет в другом процессе, и возможно даже на другом компьютере. Что это означает для нашего межпроцессного диспетчера событий?
Немного о внутреннем устройстве PyDispatcher. PyDispatcher при подписке на событие (dispatcher.connect) использует результат вызова функции id (фактически — адрес в RAM) для объекта-отправителя (sender) в качестве ключа для словаря событий. По этому ключу находится словарь, ключами которого являются объекты-события (signal), а значениями — слабые ссылки на обработчики. В результате вызова dispatcher.send диспетчер ищет обработчики события в этих словарях, сначала по ключу, значение которого равно результату вызова функции id для sender, затем по ключу signal. Использование signal в качестве ключа в словаре подразумевает хеширование этого объекта. Для объектов неизменяемых типов, таких как строки или числа, хеш-значения будут равны для равных по значению. Напротив, в тех случаях, когда объекты — экземпляры классов, хеш-значения по умолчанию будут равны их адресам в RAM, даже если объекты идентичны по содержимому.
Следовательно, если signal — экземпляр класса (как это обычно сейчас в Django и как мы будем делать в нашей реализации, об этом ниже), то восстановленный из сериализованного значения объект будет иметь отличное хеш-значение от оригинала (без дополнительных модификаций), и не будет найден диспетчером (это относится и к объектам sender).
Итак, к данным, связанным с событием.

Объект события

Мы должны обеспечить такое поведение, при котором восстановленный из сериализованного значения объект будет считаться равным тому объекту, который был использован при подписке на событие в процессе, получившем сообщение о событии из сети, и имел то же хеш-значение.
Я уже писал, что нам совсем не надо транслировать по сети все события, которые случаются в процессе. Следовательно, надо как-то выделить те события, которые транслировать надо. Для этого определим базовый клас для транслируемых событий.
''module netdispatcher.events'''
import types
class Dispatchable(object):
    def __init__(self, message=None):
        if message:
            self.message = message
   
    _message = None
    def setMessage(self, value):
        if not isinstance(value, (str, unicode, int, float, long, types.NoneType)):
            raise TypeError('The message is of the wrong type: %s'%type(value))
        self._message = value
    def getMessage(self):
        return self._message
   
    message = property(getMessage, setMessage)
   
    netDispatch = True
   
    def getName(self):
        '''Return the event name that will be wired'''
        return self.__module__+'.'+self.__class__.__name__
   
    def __eq__(self, other):
        return self.__class__ == other.__class__
   
    def __hash__(self):
        return hash(self.__module__+'.'+self.__class__.__name__)
Класс Dispatchable определен таким образом, что хеш-значения для всех объектов данного класса или его подклассов будут равны, если объекты одного класса (метод __hash__). Также все объекты данного класса и подклассов будут равны, если они одного класса (метод __eq__). Таким образом, даже восстановленный из сериализованного представления объект будет рассматриваться как равный любому другому объекту того же класса как при сравнении, так и при использовании в качестве ключа в словаре. Класс содержит свойство message, которое может использоваться для сопровождающей информации, наподобие аргумента к Exception, и может быть только неизменяемого типа. Свойство netDispatch используется как маркер при приеме события из сети. Если оно установлено в ложное значение, событие не будет транслироваться в сеть. Это свойство может быть использовано и в процессе, в котором событие возникло изначально, если вы не хотите, чтобы оно транслировалось по сети.

Объект отправителя

Мы должны обеспечить такое поведение, при котором восстановленный из сериализованого значения объект будет иметь то же значение id, что и у объекта, который был использован при вызове dispatcher.connect.
Во избежание путаницы с id, в качестве отправителя (если он передается), необходимо передавать значения, которые, будучи проинтерпретированы в другом процессе, будут иметь тот же адрес в памяти, что и уже существующие в этом процессе аналогичные объекты. Такими объектами могут служить любые объекты, которые содержатся в модулях и были созданы в процессе первого импорта модуля, а не были созданы в процессе исполнения. Удобнее и логичнее всего использовать в качестве отправителя классы и функции — при первом импорте класса или функции код этих объектов кэшируется в памяти, и при импорте этих объектов имена будут указывать на эти же объекты.
Объекты, созданные не в процессе импорта из модуля, а в процессе исполнения, использовать в качестве отправителя в нашем случае нельзя. Это касается и значений неизменяемых типов.
Это также касается и тех нечастых случаев, когда имена, ссылавшиеся на классы и функции после импорта модуля, в процессе исполнения стали ссылаться на другие объекты.
Просто условимся, что в качестве отправителя для тех событий, которые мы собираемся транслировать по сети, будет выступать либо класс, либо функция. Однако заметим, что ни то ни другое не может быть сериализовано при помощи стандартного для Python средства Pickle. Следовательно, при нам нужно написать свою процедуру сериализации и десериализации таких объектов. Сериализованное представление должно быть полным именем объекта, с указанием модуля. Десериализация должна производиться простым импортом необходимого объекта по полученному имени.

Аргументы

Здесь на первый взгляд нет подводных камней. Однако, если вспомнить, что наша цель — синхронизация данных состояния приложения, то нужно понимать, что в этих данных скорее всего будут объекты, на которые ссылаются другие части приложения внутри процесса.
Самый первый случай, который напрашивается — при вызове dispatcher.send в процессе А передать изменившийся объект, который должен быть синхронизирован по всему приложению. Но при этом, если объект будет сериализован и восстановлен в процессе Б, этот объект будет дублировать уже существующий объект в процессе Б. Мы не можем поменять этот объект, т.к. наш обработчик события может не знать всех ссылок на оригинальный объект, и поменяв известные ему сслыки, он может оставить много ссылок на старый объект, которые ему неизвестны.
Правильнее было бы искать уже существующий объект по переданным аргументам, и изменять его состояние. Следовательно, с передачей объектов в аргументах события между процессами нужно быть очень осторожным. Я предпочитаю порядок, при котором с событием, которое может быть передано по сети, передавались только аргументы неизменяемых типов, которые служат для поиска объекта в процессе и изменении его состояния по месту.

Протокол сообщений

Для трансляции событий мы должны выработать протокол. Протокол должен обеспечить сериализацию объектов, связанных с событием. Также мы можем предусмотреть служебные команды - сообщение может содержать как событие, так и управляющую команду серверу (например, регистрацию процесса на сервере). Протокол также может предоставлять дополнительные способы проверки целостности сообщения. При этом, мы можем взять в качестве транспорта механизм, который уже сам по себе обеспечивает как сериализацию, так и целостность, и служебные команды. Для краткости изложения я не буду подробно расписывать код протокола, к тому же он зависит от транспорта, который будет использоваться. От протокола нам нужны только функции encode для сериализации событий и decode для десериализации сетевых сообщений с событиями, код которых мы также опустим.
def encode(args, kwargs):
    ............

def decode(encoded):
    ............
При разработке протокола и сериализации мы должны учесть специфику данных, которые мы передаем в сообщении.
Как было сказано выше, мы диспетчирезуем события, объекты которых являются экземплярами класса Dispatchable или его подклассов. Для сериализации такого объекта можно использовать Pickle, а можно написать свою процедуру, которая преобразует объект в строку с именем класса (с модулем), экземпляром которого является объект события, и содержимое сообщения. Например, так: "netdispatcher.events.Dispatchable#message". Этой информации нам вполне хватит для того, чтобы потом инициализировать объект нужного класса с нужным значением message в процессе-приемнике.
Как мы договорились, объект отправителя должен быть либо классом, либо функцией. Мы не можем сериализовать эти объекты при помощи Pickle. Поступим так же - запишем имя класса или функции, с указанием модуля, например "some.module.Sender".
При сериализации аргументов нам нужно обеспечить, чтобы аругменты содержали только неизменяемые значения. После проверки и удаления неподходящих аргументов, мы можем сериализовать массив позиционных аргументов и словарь ключевых обычным приведением его к типу str.
Нам осталось склеить все 3 части. Можно поместить их в словарь, который потом привести к строке, либо использовать pickle для экономии места. Помните, что, возможно, в сообщение нужно будет поместить и дополнительную информацию - контрольную, либо управляющие команды, либо и то и другое.
Ну и, соответственно, процедура десериализации (decode) должна понимать как сериализованные данные првратить в рабочие объекты.
Пример реализации протокола будет дан в полном коде прототипа.

Код клиента

Напишем код клиента для нашего транспорта, с учетом всех замечаний, высказанных выше.
from netdispatcher.protocol import encode, decode
from netdispatcher.events import Dispatchable
from django.dispatch import dispatcher

class DispatchClient(DispatchBase):
    serverAddress = None
   
    def __init__(self, host, port):
        super(DispatchClient, self).__init__("", 0)
        self.setServerAddress((host, port))
        dispatcher.connect(self.handleDispatcherEvent)
        self.register()
       
    def setServerAddress(self, address):
        self.serverAddress = address
   
    def register(self):
        self.sendMessage('register')
   
    def handleDispatcherEvent(self, *args, **kwargs):
        event = kwargs['signal']
        if not isinstance(event, Dispatchable):
            return
        if not event.netDispatch:
            return

        message = encode(args, kwargs)
        if message:
            self.sendMessage(message)       
   
    def processMessage(self, data, client_addr):
        ev = decode(data)
        if not ev:
            return
       
        event, sender, args, kwargs = ev
        event.netDispatch = False
        dispatcher.send(event, sender, *args, **kwargs)
       
    def sendMessage(self, message):   
        super(DispatchClient, self).sendMessage(message, self.serverAddress)
Это упрощенный код клиента, он не содержит кода обработки ошибок и некоторых других необходимых вещей, но задачу нашу иллюстрирует.
В случае с нашим UDP-транспортом, т.к. соединения с сервером не происходит, клиент должен слушать UDP-сокет. При инициализации, мы передаем транспорту в качестве значений IP-адреса и порта пустую строку и 0 соответственно, т.о. мы будем слушать первый доступный сокет. Мы записываем адрес и порт сервера, подписываем метод handleDispatcherEvent в качестве обработчика всех событий диспетчера PyDispatcher и посылаем строку register серверу, чтобы он включил клиента в свой список.
Метод handleDispatcherEvent при поступлении события проверяет, относится ли оно к классу событий, которые могут транслироваться по сети (должен быть объект класса Dispatchable или его подкласса), проверяет значение атрибута netDispatch объекта-события (оно должно быть истинным), и если все совпало, сериализует событие в сообщение при помощи функции encode и отправляет сообщение серверу.
Метод processMessage десериализует сообщение, и если пришло сообщение с событием, вызывает dispatcher.send, предварительно установив значение атрибута netDispatch события в False для избежания зацикливания.

Запуск сервера

Код нашего транспорта использует модуль asyncore. Для начала работы такого сервера надо запустить цикл событий asyncore. Делается это вызовом функции asyncore.loop. Напишем простейший скипт для старта сервера.
import asyncore
from netdispatcher import DispatchMasterServer
server = DispatchMasterServer('127.0.0.1', 9999)
asyncore.loop()
Этот скрипт очень простой, никакие внешние параметры конфигурации не задаются и не используются. Сервер будет слушать порт 9999 на IP-адресе 127.0.0.1 (localhost). Он будет работать в качестве процесса переднего плана, а не демона.
Для реального сервера наверняка потребуется бoльшая гибкость - конфигурирование значений адреса, порта; процедура ухода в фоновый режим; запись в логи и т.п. Данные аспекты не имеют отношения к данной статье, поэтому мы их опустим. В архиве с исходным кодом скрипт для запуска сервера допускает гораздо большую гибкость.

Запуск клиента

Если мы еще не забыли, для чего все это делалось (:), то помним, что клиент в нашем случае должен работать внутри процесса веб-приложения, и его задача - отправлять серверу сообщения о событиях внутри процесса и транслировать сообщения от других процесов, полученные через сервер, в события процесса. Соответственно, клиент должен запускаться при старте процесса.
Те, кто знаком с asyncore, наверняка знают, что вызов asyncore.loop блокирует исполнение кода, написанного после этого вызова, на время работы цикла asyncore. Нам это не очень-то подходит — наше приложение должно заниматься не только приемом входящих сообщений о событиях, а делать еще что-нибудь, для чего оно и было создано. Для того, чтобы asyncore.loop не блокировал наше приложение, а работал параллельно с обработкой запросов, запустим его в отдельном потоке исполнения. Напишем простую процедуру запуска клиента.
import asyncore, threading
from netdispatcher import DispatchClient
 
client = DispatchClient('127.0.0.1', 9999)

class AsyncEventLoop (threading.Thread):
    def run(self):
        asyncore.loop()
       
evLoop = AsyncEventLoop()
evLoop.start()
В этом скрипте мы инициализируем клиента, передавая ему адрес и порт сервера (подразумеваем, что сервер крутится на том же компьютере), инициализируем поток исполнения, в котором будет крутиться цикл событий asyncore, и запускаем этот поток. C этого момента клиент ждет событий от PyDispatcher, чтобы отправить серверу сообщения о них, и ждет сообщений от сервера, извещающих о событиях в других процессах. Все? Нет.
Нам еще необходимо встроить клиент в процесс приложения, для того, чтобы он работал с пользой. Эта процедура зависит от реализации самого приложения, от того, как оно создает процессы. Возможно, в каком-то приложении было бы достаточно импортировать скрипт запуска клиента, и все бы заработало. Возможны и другие сценарии.
Я начал эту серию, говоря о приложениях, построенных на Django, использующих FastCGI. Опишу способ, при помощи которого можно встроить клиента в такой процесс Django. Нам нужно сделать так, чтобы каждый новый процесс Django заводил нашего клиента либо при старте, либо перед тем, как будет обработан первый запрос. Для того, чтобы запустить клиента при старте, нужно переделывать внутреннюю процедуру Django для запуска проессов FastCGI, а этого делать не хочется. С другой стороны, если запускать клиента в начале обработки первого запроса, то это можно решить и без изменения кода Django. В любом случае, до момента обработки первого запроса процесс содержит только базовый код Django, и не содержит никаких данных и кода, специфичного для приложения, поэтому нам такой способ запуска подходит.
Т.о. нам нужно вставить процедуру запуска клиента где-то в коде нашего приложения так, чтобы она сработала единожды, причем в начале обработки первого запроса. Как нам перехватить этот момент? При помощи механизма Django middleware. Middleware запускаются в начале каждого запроса. Нам нужно написать свой middleware так, чтобы процедура инициализации клиента происходила только при первом запросе, и добавить его в список MIDDLEWARE_CLASSES в самом начале (по крайней мере до тех middleware, которые могут генерировать интересные нам события). Т.к. все middleware из списка запускаются перед обработкой каждого запроса, для того, чтобы обеспечить однократный запуск нашего клиента, создадим модуль-контейнер с единственной переменной, которая при самом первом импорте модуля в процессе будет пуста, а при обработке первого запроса ссылалась на объект нашего клиента. При последующих запросах наш middleware будет проверять значение этой переменной. После первого запроса она будет не пуста, если клиент был правильно проинициализирован.
Модуль-контейнер:
'''module netdispatcher.middleware.clientstatus'''
client = None

Middleware:
'''module netdispatcher.middleware'''
from netdispatcher import DispatchClient
import asyncore, threading

class AsyncEventLoop (threading.Thread):
    def run(self):
        asyncore.loop()
       
class DispatchClientMiddleware(object):
    def process_request(self, request):
        from netdispatcher.middleware import clientstatus
        if clientstatus.client:
            return None
       
        clientstatus.client = DispatchClient('127.0.0.1', 9999)
        evLoop = AsyncEventLoop()
        evLoop.start()
        return None
Добавляем "netdispatcher.middleware.DispatchClientMiddleware" в начало списка MIDDLEWARE_CLASSES в settings.py нашего проекта.
После перезапуска Django наш клиент начнет работать в каждом из запущенных процессов после того, как им будет передан на обработку первый запрос. Он будет обмениваться сообщениями с сервером по протоколу UDP, транслируя события процесса в сеть и события других процессов в свой процесс.

Что дальше

Для иллюстрации был приведен пример транспорта, основанного на протоколе UDP. Он очень легкий, простой и быстрый по сравнению с TCP, однако его достоинства являются также причиной его основного недостатка. Этот протокол не гарантирует доставку сообщений и порядок их доставки. В условиях высокой нагрузки на сетевую подсистему пакеты UDP могут теряться. Конечно, можно разработать более-менее надежный протокол поверх UDP, но высока вероятность, что выигрыш в производительности от использования UDP будет уничтожен производительностью протокола, построенного на его базе, поэтому реализованный транспорт не рекомендуется использовать в боевых условиях (тогда как для тестирования работы диспетчеризации под небольшой нагрузкой он вполне подходит). Для надежной доставки сообщений можно было бы реализовать транспорт на основе TCP самостоятельно, при помощи того же asyncore, либо с использованием существующих решений для обмена сообщениями по сети. Исходный код, ссылка на который дана ниже, содержит код транспорта, основанного на системе обмена сообщениями Spread. Такой транспорт больше подходит для надежного обмена сообщениями, и может быть использован под большой нагрузкой. Более подробно о Spread и транспорте, на нем основаном, я напишу в другой статье.
Также исходный код, ссылка на который дана ниже, несколько отличается от тех примеров, которые я дал в этой статье. В нем заложена возможность конфигурирования системы, вывод отладочной информации, несколько изменены реализация, интерфейс классов и вызова функций и методов.
Код распространяется под лицензией BSD. Автор с удовольствием услышит комментарии и предложения, касающиеся данной разработки, а также о том, как вы будете ее применять на практике.
Полный исходный код рабочего прототипа межпроцессного диспетчера событий.         Возможности получения результатов интерпретации Python-выражений в C-программу будут рассмотрены в следующих примерах.

Источники:
        www.ragbag.ru/2006/10/09/python_netdispatcher2/

Автор: Деркачев М.   
ПОМОЩЬ САЙТУ :
sms.Є®ЇЁ«Є  *PythonUA*
Для чего Вы используете Python?
Admin( 108 )
Web( 149 )
GUI( 105 )
Embedding ( 65 )
Другое( 103 )
Какими продуктами Вы пользовались?
Zope( 67 )
Plone( 40 )
TG( 35 )
Django( 75 )
Twisted( 38 )
Другими( 54 )
ДРУЗЬЯ:
EXCEPTION.ORG.UA
LUG.DN.UA
D-FENS.ORG.UA
SLAV0NIC.XSS.RU
CETUS.COM.UA
TOPHOST.COM.UA
[Python Powered]
Copyright © 2006 python.com.ua