От Perl-скрипта к Twisted-приложению: Черновик
Итак, в наличии есть работающий Perl-скрипт и некое описания протокола (некое,
потому что это логи уже работающего "концентратора" и небольшие комментарии по
тексту).
Согласно принятому дизайну Twisted-приложений, общий
функционал разбивается на модули, причем разделяется реализация собственно
протокола и "сторонней логики". В нашем случае, "сторонняя логика" - это
выборка имени человека по номеру договора.
Поскольку у товарища стоит такая СУБД, которой у меня нет, да и особо ставить
не хочется, я ввел, опять-таки общепринятый для Twisted-приложений, механизм
интерфейсов и классов, реализующих их.
Поясню: мне необходимо на основании одних данных (номера договора) получать
другие данные (фамилию, имя и отчество человека). Сделать это можно различными
способами, данные могут храниться в различных СУБД и т.д. Поэтому я создаю
класс-описание, интерфейс, который описывает какие методы и атрибуты
необходимы для реализации, но саму реализацию не указывает. Общепринято такие
классы-интерфейсы называть "IName", где Name — общее имя интерфейса. У меня
получился такой интерфейс:
from zope.interface import Interface, Attribute
class IClient(Interface):
"""An object that returns info about client"""
encoding = Attribute("Encoding of client's backend")
def getClient(agreem_number):
"""Returns an info about client"""
В Twisted используется механизм интерфейсов из Zope. В отличии
от "обычных" классов, в классах-интерфейсах не надо в методах ставить
первым параметром self.
Итак, теперь у нас есть описание, каким должен быть класс: у него должен быть
атрибут encoding (потому что кодировка СУБД может не совпадать с кодировкой
транспорта) и метод getClient, получающий один параметр agreem_number.
Далее, реализуем "тупой" клиент в тестовых целях.
import time
from zope.interface import implements
# полагаем, что IClient у нас либо импортирован, либо находится
# в том же модуле
class DummyClient(object):
"""Dummy client for testing purposes"""
implements(IClient)
def __init__(self, encoding=None):
self.encoding = encoding
self.pause_time = 0
def getClient(self, agreem_num):
res = 'Dummy_%s' % agreem_num
# в описании было одно требование: чтобы возвращаемый результат
# не был длиннее 20 символов
if len(res) > 20:
res = res[:20]
# для имитации задержки выборки данных
time.sleep(self.pause_time)
return res
Теперь у нас уже есть работающий клиент, так что пора браться за реализацию
протокола. Поскольку в нашем случае протокол текстовый, т.е. данные передаются
построчно, то я за основу взял LineReceiver.
from twisted.protocols import basic
from twisted.python import log
class PythyProto(basic.LineReceiver):
"""Simple text demo protocol"""
def connectionMade(self):
log.msg("got connection from %s" % str(self.transport.client))
def connectionLost(self, reason):
log.msg("connection from %s lost" % str(self.transport.client))
def lineReceived(self, line):
log.msg("data received from %s: `%s'" % (str(self.transport.client), line))
if line == '':
# для тестовых целей
self.sendLine("Good bye")
self.transport.loseConnection()
return
agr = line[10:15]
client = self.factory.clients.getClient(agr)
self.sendAnswer(client)
def sendAnswer(self, client):
packet = "dummypacketmaker012345678%s" % client
self.sendLine(packet)
def sendLine(self, line):
assert isinstance(line, basestring)
line = line.replace('r', '').replace('n', '')
log.msg("data send: %s" % line)
line = line + "rn"
self.transport.write(line)
Экземпляр класса протокол создается для каждого соединения. Т.е. некая неизменная
информация (например, кодировка данного протокола) не сохраняется, для сохранения
такого рода информации существуют "фабрики" (factory):
from twisted.internet import protocol
# полагаем, что PythyProto у нас либо импортирован, либо находится
# в том же модуле
class PythyFactory(protocol.ServerFactory):
protocol = PythyProto # class, not instance!
def __init__(self, clients, transport_encoding):
self.clients = clients
self.encoding = transport_encoding
Теперь собираем всё "в кучу" и пробуем запустить:
import sys
from twisted.internet import reactor
from twisted.python import log
from TwistedPythy import proto, clients
log.startLogging(sys.stderr)
client = clients.DummyClient()
client.pause_time = 0
factory = proto.PythyFactory(client, 'utf-8')
reactor.listenTCP(3000, factory)
reactor.run()
Первый рабочий "черновик" создан. Пока главное, что он вообще работает :-)
Кодировки будут следующим этапом.
А для разминки советую попробовать поставить паузу в DummyClient секунд в
10-20 и попробовать одновременно сделать запрос двумя клиентами.
P.S. К сожалению, движок блога не дает загрузить архив исходников, поэтому я загрузил на RapidShare.
Источники:
gorod-omsk.ru/blog/pythy/
|
ПОМОЩЬ САЙТУ :
|
|
|