Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Secure Programming for Li...6507 
 Linux Kernel 2.6...5280 
 Trees...1119 
 Максвелл 3...1051 
 William Gropp...990 
 Go Web ...963 
 Ethreal 3...930 
 Ethreal 4...918 
 Gary V.Vaughan-> Libtool...914 
 Ext4 FS...905 
 Clickhouse...902 
 Rodriguez 6...900 
 Ethreal 1...897 
 Steve Pate 1...886 
 C++ Patterns 3...864 
 Assembler...854 
 Ulrich Drepper...844 
 DevFS...788 
 MySQL & PosgreSQL...774 
 Стивенс 9...758 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Server

Обзор

Twisted позволяет писать нагруженные серверы. В твистед есть несколько слоев.

Этот документ описывает т.н. Protocol слой. Речь пойдет о TCP, SSL и Unix socket servers, для UDP смотрите тут.

Ваш протокол будет управлять классом, производным от twisted.internet.protocol.Protocol. Большинство протоколов унаследованы от этого класса. Инстанс такого протокола может быть запущен при коннекте , или в любой произвольный момент.

Конфигурация поведения протокола прописывается в классе Factory, который унаследован от twisted.internet.protocol.Factory. По умолчанию фабрика - factory - запускает каждый протокол, и устанавливает ему атрибут, называемый factory, который указывает на себя.

Обычно это полезно, когда на разные порты или разные сетевые адреса привязываются разные сервисы. Factory ничего не знает про коннект и про сеть.

Протоколы

Протокол управляет данными асинхронно. Протокол никогда не ждет события, он их обрабатывает,когда они появляются.

Вот простой пример:

from twisted.internet.protocol import Protocol
 
 class Echo(Protocol):
 
     def dataReceived(self, data):
         self.transport.write(data)
 

Это простейший протокол. Он просто пишет назад то, что пишет ему клиент, и не отвечает ни на какие события. Вот пример протокола, отвечающего на события:

from twisted.internet.protocol import Protocol
 
 class QOTD(Protocol):
 
     def connectionMade(self):
         self.transport.write("An apple a day keeps the doctor away\r\n") 
         self.transport.loseConnection()
 

Этот протокол отвечает на коннект тем, что обрывает его.

Событие connectionMade происходит при коннекте. Событие connectionLost срабатывает при разрыве коннекта. Пример:

from twisted.internet.protocol import Protocol
 
 class Echo(Protocol):
 
     def connectionMade(self):
         self.factory.numProtocols = self.factory.numProtocols+1 
         if self.factory.numProtocols > 100:
             self.transport.write("Too many connections, try later") 
             self.transport.loseConnection()
 
     def connectionLost(self, reason):
         self.factory.numProtocols = self.factory.numProtocols-1
 
     def dataReceived(self, data):
         self.transport.write(data)
 

connectionMade и connectionLost работают с числом активных протоколов. connectionMade немедленно закрывает коннект при превышении квоты.

Использование Protocol

Вот пример кода сервера QOTD:

from twisted.internet.protocol import Protocol, Factory
 from twisted.internet import reactor
 
 class QOTD(Protocol):
 
     def connectionMade(self):
         self.transport.write("An apple a day keeps the doctor away\r\n") 
         self.transport.loseConnection()
 
 # Next lines are magic:
 factory = Factory()
 factory.protocol = QOTD
 
 # 8007 is the port you want to run under. Choose something >1024
 reactor.listenTCP(8007, factory)
 reactor.run()
 

Helper Protocols

Наиболее популярный интернет-протокол имеет line-based основу. Строки обычно заканчиваются комбинацией символов CR-LF.

Есть протоколы смешанные, у них есть как line-based секция, так и raw data секция, например HTTP/1.1 и Freenet протоколы.

Для этого в твистед есть LineReceiver протокол. У него есть два event handlers - lineReceived и rawDataReceived. По умолчанию, будет работать только lineReceived , один раз на каждую строку. Если вызвать setRawMode , протокол будет вызывать rawDataReceived до тех пор, пока будет вызываться setLineMode, который использует lineReceived.

Пример line receiver:

from twisted.protocols.basic import LineReceiver
 
 class Answer(LineReceiver):
 
     answers = {'How are you?': 'Fine', None : "I don't know what you mean"}
 
     def lineReceived(self, line):
         if self.answers.has_key(line):
             self.sendLine(self.answers[line])
         else:
             self.sendLine(self.answers[None])
 

Здесь разделитель не является частью строки.

Есть также протокол, основанный на netstring, и prefixed-message-length протокол.

State Machines

Во многих твистед протоколах хэндлерам нужно сохранять состояние.

  • Не нужно этим увлекаться. Записываемое состояние должно быть связано с одним уровнем абстракции.
  • Нужно использовать динамичность питона - см. пример SMTP client.
  • Не нужно смешивать код уровня приложения с кодом обработки событий протокола.

Factories

Базовым классом обычно является класс twisted.internet.protocol.Factory. Иногда возникает необходимость factory-specific конфигурации для протокола. В этих случаях нужно использовать класс Factory.

from twisted.internet.protocol import Factory
 from twisted.protocols.wire import Echo
 
 myFactory = Factory()
 myFactory.protocol = Echo
 

Для настройки конфигурации:

from twisted.internet.protocol import Factory, Protocol
 
 class QOTD(Protocol):
 
     def connectionMade(self):
         self.transport.write(self.factory.quote+'\r\n')
         self.transport.loseConnection()
 
 
 def makeQOTDFactory(quote=None):
     factory = Factory()
     factory.protocol = QOTD
     factory.quote = quote or 'An apple a day keeps the doctor away'
     return factory
 

Пример фабрики, которая позволяет протоколу писать лог файл:

from twisted.internet.protocol import Factory
 from twisted.protocols.basic import LineReceiver
 
 
 class LoggingProtocol(LineReceiver):
 
     def lineReceived(self, line):
         self.factory.fp.write(line+'\n')
 
 
 class LogfileFactory(Factory):
 
     protocol = LoggingProtocol
 
     def __init__(self, fileName):
         self.file = fileName
 
     def startFactory(self):
         self.fp = open(self.file, 'a')
 
     def stopFactory(self):
         self.fp.close()
 

Итог

А теперь конфигурируемый QOTD сервер :

from twisted.internet.protocol import Factory, Protocol
 from twisted.internet import reactor
 
 class QOTD(Protocol):
 
     def connectionMade(self):
         self.transport.write(self.factory.quote+'\r\n')
         self.transport.loseConnection()
 
 
 class QOTDFactory(Factory):
 
     protocol = QOTD
 
     def __init__(self, quote=None):
         self.quote = quote or 'An apple a day keeps the doctor away'
 
 reactor.listenTCP(8007, QOTDFactory("configurable quote"))
 reactor.run()
 

Client

Обзор

Твистед позволяет писать гибкие клиенты. Это достигается за счет нескольких слоев. В этом документе описываются клиенты TCP, SSL, Unix sockets

Протокол обычно унаследован от twisted.internet.protocol.Protocol. Большинство хэндлеров унаследовано от этого класса. Инстанс протокола создается при коннекте и удаляется при дисконнекте.

Конфигурация хранится в фабрике, которая обычно унаследована от twisted.internet.protocol.ClientFactory. Фабрика запускает протокол и устанавливает свой собственный атрибут. Это позволяет протоколу настраивать конфигурацию.

Protocol

Протокол управляет данными в асинхронной манере. Протокол никогда не ждет событий, и отвечает на них, лишь когда они приходят из сети.

Вот пример:

from twisted.internet.protocol import Protocol from sys import stdout class Echo(Protocol): def dataReceived(self, data): stdout.write(data)

Это простейший протокол, который просто печатает то, что читает. За кадром осталось еще много событий, на которые данный протокол просто не отвечает. Вот другой пример:

from twisted.internet.protocol import Protocol class WelcomeMessage(Protocol): def connectionMade(self): self.transport.write("Hello server, I am the client!\r\n") self.transport.loseConnection()

Этот протокол устанавливает коннект с сервером, посылает ему сообщение и разрывает коннект.

Событие connectionMade обычно происходит в момент старта протокола. Обьекты протокола заканчивают свою жизнь в connectionLost.

Одиночный клиент

Часто протоколу нужно только приконнектиться к серверу В этом случае класс twisted.internet.protocol.ClientCreator дает нужное API.

from twisted.internet import reactor from twisted.internet.protocol import Protocol, ClientCreator class Greeter(Protocol): def sendMessage(self, msg): self.transport.write("MESSAGE %s\n" % msg) def gotProtocol(p): p.sendMessage("Hello") reactor.callLater(1, p.sendMessage, "This is sent in a second") reactor.callLater(2, p.transport.loseConnection) c = ClientCreator(reactor, Greeter) c.connectTCP("localhost", 1234).addCallback(gotProtocol)

ClientFactory

Мы используем reactor.connect* и ClientFactory. ClientFactory - фабрика создающая протокол, она получает события в соответствии со статусом. Это позволяет фабрике делать реконнект к серверу в случае ошибки. Вот пример ClientFactory, использующий эхо-протокол и выводящий статус коннекта:

from twisted.internet.protocol import Protocol, ClientFactory from sys import stdout class Echo(Protocol): def dataReceived(self, data): stdout.write(data) class EchoClientFactory(ClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' return Echo() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason

При коннекте:

from twisted.internet import reactor reactor.connectTCP(host, port, EchoClientFactory()) reactor.run()

clientConnectionFailed вызывается, когда коннект не может быть установлен, clientConnectionLost вызывается после того, как коннект был сделан, а потом потерян.

Reconnection

Часто коннект теряется клиентом. При этом нужно вызвать connector.connect():

from twisted.internet.protocol import ClientFactory class EchoClientFactory(ClientFactory): def clientConnectionLost(self, connector, reason): connector.connect()

Коннектор передается в качестве первого аргумента в интерфейсе между коннектом и протоколом. Когда коннект теряется и фабрика получает событие clientConnectionLost , она делает вызов connector.connect() .

Для такой логики нужно имплементировать ReconnectingClientFactory , что позволить делать реконнект до тех пор , пока он не будет получен.

Вот пример ReconnectingClientFactory:

from twisted.internet.protocol import Protocol, ReconnectingClientFactory from sys import stdout class Echo(Protocol): def dataReceived(self, data): stdout.write(data) class EchoClientFactory(ReconnectingClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' print 'Resetting reconnection delay' self.resetDelay() return Echo() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason ReconnectingClientFactory.clientConnectionLost(self, connector, reason) def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)

A Пример: ircLogBot

Обзор ircLogBot

Пример посложнее можно найти в каталоге Twisted word doc/examples : examples

 

# twisted imports from twisted.words.protocols import irc from twisted.internet import reactor, protocol from twisted.python import log # system imports import time, sys class MessageLogger: """ An independent logger class (because separation of application and protocol logic is a good thing). """ def __init__(self, file): self.file = file def log(self, message): """Write a message to the file.""" timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time())) self.file.write('%s %s\n' % (timestamp, message)) self.file.flush() def close(self): self.file.close() class LogBot(irc.IRCClient): """A logging IRC bot.""" nickname = "twistedbot" def connectionMade(self): irc.IRCClient.connectionMade(self) self.logger = MessageLogger(open(self.factory.filename, "a")) self.logger.log("[connected at %s]" % time.asctime(time.localtime(time.time()))) def connectionLost(self, reason): irc.IRCClient.connectionLost(self, reason) self.logger.log("[disconnected at %s]" % time.asctime(time.localtime(time.time()))) self.logger.close() # callbacks for events def signedOn(self): """Called when bot has succesfully signed on to server.""" self.join(self.factory.channel) def joined(self, channel): """This will get called when the bot joins the channel.""" self.logger.log("[I have joined %s]" % channel) def privmsg(self, user, channel, msg): """This will get called when the bot receives a message.""" user = user.split('!', 1)[0] self.logger.log("<%s> %s" % (user, msg)) # Check to see if they're sending me a private message if channel == self.nickname: msg = "It isn't nice to whisper! Play nice with the group." self.msg(user, msg) return # Otherwise check to see if it is a message directed at me if msg.startswith(self.nickname + ":"): msg = "%s: I am a log bot" % user self.msg(channel, msg) self.logger.log("<%s> %s" % (self.nickname, msg)) def action(self, user, channel, msg): """This will get called when the bot sees someone do an action.""" user = user.split('!', 1)[0] self.logger.log("* %s %s" % (user, msg)) # irc callbacks def irc_NICK(self, prefix, params): """Called when an IRC user changes their nickname.""" old_nick = prefix.split('!')[0] new_nick = params[0] self.logger.log("%s is now known as %s" % (old_nick, new_nick)) # For fun, override the method that determines how a nickname is changed on # collisions. The default method appends an underscore. def alterCollidedNick(self, nickname): """ Generate an altered version of a nickname that caused a collision in an effort to create an unused related name for subsequent registration. """ return nickname + '^' class LogBotFactory(protocol.ClientFactory): """A factory for LogBots. A new protocol instance will be created each time we connect to the server. """ # the class of the protocol to build when new connection is made protocol = LogBot def __init__(self, channel, filename): self.channel = channel self.filename = filename def clientConnectionLost(self, connector, reason): """If we get disconnected, reconnect to server.""" connector.connect() def clientConnectionFailed(self, connector, reason): print "connection failed:", reason reactor.stop() if __name__ == '__main__': # initialize logging log.startLogging(sys.stdout) # create factory protocol and application f = LogBotFactory(sys.argv[1], sys.argv[2]) # connect factory to this host and port reactor.connectTCP("irc.freenode.net", 6667, f) # run bot reactor.run()

ircLogBot.py коннектится к IRC server, присоединяет канал, и логирует весь трафик в файл. Используется реконнект при потере.

Persistent Data in the Factory

Поскольку протокол каждый раз при коннекте создается заново, клиенту нужно сохранять данные . В данном случае нужно помнить о том, какой irc-канал логируется, и куда ведется логирование.

from twisted.internet import protocol from twisted.protocols import irc class LogBot(irc.IRCClient): def connectionMade(self): irc.IRCClient.connectionMade(self) self.logger = MessageLogger(open(self.factory.filename, "a")) self.logger.log("[connected at %s]" % time.asctime(time.localtime(time.time()))) def signedOn(self): self.join(self.factory.channel) class LogBotFactory(protocol.ClientFactory): protocol = LogBot def __init__(self, channel, filename): self.channel = channel self.filename = filename

Когда протокол создается, он получает ссылку на фабрику как self.factory.

Что еще почитать

Класс Protocol используется как имплементация IProtocol. Для изучения интерфейса IProtocol , смотрите API documentation IProtocol.

Атрибут transport унаследован от интерфейса ITCPTransport. Смотрите API documentation для ITCPTransport.

По интерфейсам также смотрите Components: Interfaces and Adapters.

Эхо-сервер

Код простого эхо-сервера в твистед получается компактным:
 from twisted.internet import reactor
 from twisted.internet.protocol import ServerFactory 
 from twisted.protocols.basic import LineOnlyReceiver 
 
 class ChatProtocol(LineOnlyReceiver): 
 
     name = "" 
 
     def getName(self): 
         if self.name!="": 
             return self.name 
         return self.transport.getPeer().host 
 
     def connectionMade(self): 
         print "New connection from "+self.getName() 
         self.sendLine("Welcome to my my chat server.") 
         self.sendLine("Send '/NAME [new name]' to change your name.") 
         self.sendLine("Send '/EXIT' to quit.") 
         self.factory.sendMessageToAllClients(self.getName()+" has joined the party.") 
         self.factory.clientProtocols.append(self)
 
     def connectionLost(self, reason): 
         print "Lost connection from "+self.getName() 
         self.factory.clientProtocols.remove(self) 
         self.factory.sendMessageToAllClients(self.getName()+" has disconnected.") 
 
     def lineReceived(self, line): 
         print self.getName()+" said "+line 
         if line[:5]=="/NAME": 
             oldName = self.getName() 
             self.name = line[5:].strip() 
             self.factory.sendMessageToAllClients(oldName+" changed name to "+self.getName()) 
         elif line=="/EXIT": 
             self.transport.loseConnection() 
         else: 
             self.factory.sendMessageToAllClients(self.getName()+" says "+line) 
 
     def sendLine(self, line): 
         self.transport.write(line+"\r\n") 
 
 class ChatProtocolFactory(ServerFactory): 
 
     protocol = ChatProtocol 
 
     def __init__(self): 
         self.clientProtocols = [] 
 
     def sendMessageToAllClients(self, mesg): 
         for client in self.clientProtocols:
             client.sendLine(mesg) 
 
 print "Starting Server"
 factory = ChatProtocolFactory()
 reactor.listenTCP(12345, factory)
 reactor.run() 
 
В качестве клиента можно использовать телнет:
 >> telnet localhost 12345
 
При коннекте каждый клиент будет создавать на сервере свой экземпляр протокола. При этом клиент внутри протокола ничего не знает о других клиентах. Где хранить клиентов? Мы для этого используем фабрику:
     def connectionMade(self): 
         ...
         self.factory.clientProtocols.append(self)
 
Добавим в фабрику атрибут - список клиентов clientProtocols: в конструкторе фабрики мы этот список и определим:
     def __init__(self): 
         self.clientProtocols = [] 
 
Также фабрика будет заниматься широковещательной рассылкой каждого сообщения от каждого клиента:
 def sendMessageToAllClients(self, mesg): 
         for client in self.clientProtocols:
             client.sendLine(mesg)
 

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье