客戶端
node
綜述python
twisted是一個設計很是靈活的框架,經過它能夠寫出功能強大的客戶端,然而要在代碼中使用很是多的層次結構。這個文檔包括建立用於TCP,SSL和Unix sockets的客戶端
在 底層,實際上完成協議語法和處理的是Protocol類。這個類一般是來自於twisted.internet.protocol.Protocol。大 多數的protocol handlers繼承自這個類或它的子類。protocol類的一個實例將在你鏈接到服務器時被初始化,在斷開鏈接時結束。這意味着持久的配置不會被保存 在Protocol中。
持久的配置將會保存在Factory類中,它一般繼承自 twisted.internet.protocol.Factory(或者 twisted.internet.protocol.ClientFactory)。默認的factory類僅僅實例化Protocol,而且設置 factory屬性指向本身。這使得Protocol能夠訪問、修改和持久配置。
Protocol react
這個類將會是代碼中使用最多的類,是twisted異步處理數據的一個協議,這意味着這個協議從不等待一個事件,它只是響應從網絡中到來的事件。服務器
下面是一個簡單的例子:
網絡
from twisted.internet.protocol import Protocol from sys import stdout class Echo(protocol): def dataReceived(self,data): stdout.write(data)
這是一個簡單的例子,將從鏈接進來的程序讀到的數據顯示到標準輸出上。下面的例子中Protocol會相應另一個事件:app
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一般在Protocol對象出現時建立,connectionLost在Protocol斷開時建立。
異步
簡單的,單用戶客戶端socket
大多數狀況,protocol僅須要鏈接服務器一次,而且代碼只是想得到一個protocol的鏈接實例。正是因爲這些緣由,twisted.internet.endpoints提供了一些合適的API,特別是其中的connectProtocol提供了一個protocol實例而不是factory。ide
from twisted.internet import reactor from twisted.internet.protocol import Protocol from twisted.internet.endpoints import TCP4ClientEndpoint,connectProtocol 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) point = TCP4ClientEndpoint(reactor, "localhost", 1234) d = connectProtocol(point, Greeter()) d.addCallback(gotProtocol) reactor.run()
ClientFactory
from twisted.internet.protocol import Protocol, ClientFactory from sys import stdout class Echo(Protocol): def dataReceived(self, connector): 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 clientConnectionFaild(self, connector, reason): print 'Connection failded. Reason', reason
要鏈接這個EchoClientFactory到一個服務器上,可使用下面的代碼:
from twisted.internet import reactor reactor.connectTCP(host, port, EchoClientFactory()) reactor.run()
注意:函數connectConnectionFailed在鏈接不
能established別調用,而clientConnectionLost在鏈接完成而且斷開時被調用。
Reactor Client APIs
connectTCP
IReactorTCP.connectTCP 提供對IPV4和IPV6客戶端的支持,它接收的host參數能夠是主機名,也能夠是IP地址,在鏈接以前reactor會自動的將主機名解析爲IP地址。這也意味着若是一個主機名有多個IP地址解析時,從新鏈接時不必定老是能鏈接到相同的主機。這說明在每次鏈接以前都會進行域名解析。若是建立了不少短鏈接(每秒幾百次或幾千次),它會首先將主機名解析爲IP地址,而後將地址傳給connectTCP。
Reconnection
一般,當一個客戶端鏈接由於網絡問題中斷的時候,在斷開以後一個從新鏈接的方法叫作connector.connect()
from twisted.internet.protocol import ClientFactory class EchoClientFactory(ClientFactory): def clientConnectionLost(self, connector, reason): connector.connect()
當鏈接出錯,同時factory收到一個clientConnectionLost的事件通知時,factory經過調用connector.connect()來從新啓動一個鏈接。然而,大多數程序想要實現重連這個功能應該實現ReconnectingClientFactory,下面是一個經過ReconnectingClientFactory實現的Echo protocol:
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 'Reseting reconnection delay' self.resetDelay() return Echo() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason ReconnectingClientFactory.clientConnectionLost(self, connector, reason) def clientConnectionFailded(self, connector, reason): print 'Connection failded. Reason:', reason ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
高級例子:ircLogBot
上面客戶端的例子都很簡單,下面是一個更加複雜的例子
ircLogBot.py
# Copyright (c) Twisted Matrix Laboratories.# See LICENSE for details."""An example IRC log bot - logs a channel's events to a file.If someone says the bot's name in the channel followed by a ':',e.g. <foo> logbot: hello!the bot will reply: <logbot> foo: I am a log botRun this script with two arguments, the channel name the bot shouldconnect to, and file to log to, e.g.: $ python ircLogBot.py test test.logwill log channel #test to the file 'test.log'.To run the script: $ python ircLogBot.py <channel> <file>"""# twisted importsfrom twisted.words.protocols import ircfrom twisted.internet import reactor, protocolfrom twisted.python import log# system importsimport time, sysclass 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. """ def __init__(self, channel, filename): self.channel = channel self.filename = filename def buildProtocol(self, addr): p = LogBot() p.factory = self return p 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服務器,鏈接一個通道,全部的日誌和通訊經過它傳送到一個文件。
Persistent Data in the Factory
因爲Protocol實例在每次鏈接創建的時候都會從新建立,客戶端須要對一些須要持久鏈接的數據進行追蹤。
from twisted.woeds.protocols import irc from twisted.internet import protocol class LogBot(irc.IRCClient): def connectionMade(self): irc.IRCClient.connectionMade(self) self.logger = MessageLogger(open(self.factory.filename, "a")) self.log.log("[connected at %s]" % time.asctime(time.localtime(time.time()))) def signedOn(self): self.join(self.factory.channel) class LogBotFactory(protocol.ClientFactory): def __init__(self, channel, filename): self.channel = channel self.filename = filename def buildProtocol(self, addr): p = LogBot() p.factory = self return p
當protocol建立時,會獲得一個factory的引用self.factory,它能訪問factory的屬性。在logBot的例子中,它打開文件並鏈接到存儲在factory中的通道。