Twisted教程-客户端

原文:http://www.klipdas.com/blog/?p=twisted-clients

客户端

2.2.1 概述.

Twisted框架设计的很灵活,可以编写功能强大的客户端。灵活的代价在于编写客户端方法的多一些层次,文档涵盖了使用TCP,SSL和Unix sockets,UDP来创建客户端,它们分别都有介绍(92页)。

基本上,实际实现协议的解析和处理的是在Protocol类中。该类通常继承至twisted.internet.protocol.Protocol,大多数协议处理程序要么继承至该类,要么是其子类。当你连接到服务器时协议类就会实例化一个实体,在你离开的时候,被释放。就是说持久的配置不会驻留在Protocol中。

持久配置被保存在Factory类中,它通常继承至twisted.internet.protocol.ClientFactory。默认的工厂类只实例化Protocol,并赋值给它的factory属性,这就允许Protocol访问,有可能修改持久化配置。

2.2.2 协议

如上所述,协议、辅助类和函数是大部分代码。Twisted协议以异步形式处理数据,就是说协议从不等待事件,而是在收到事件时回复。 示例:

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

这是一个最简单的协议,只是简单的将接收到的数据写入标准输出中去,还有很多的事件它没有响应。下面时候Protocol响应其他事情的示例:

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对象设置的地方,同时初始化问候语(就像上面的WelcomeMessage协议)。特定的Protocol对象会在connectionLost事件中被清除。

2.2.3 简易单用户客户端

很多情况下,协议只需要连接服务器一次,程序只想获取一个协议连接的实例。这些情况下,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)

2.2.4 ClientFactory

我们使用reactor.connect*和ClientFactory,该ClientFactory负责创建Protocol,并接收与连接状态相关的事件,它可以做一些工作,如在连接错误事件中进行重新连接。下面是一个简单ClientFactory的示例,它使用Echo协议(见上述)并打印出连接状态。

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

为使EchoClientFactory连接服务器,添加如下代码:

from twisted.internet import reactor

reactor.connectTCP(host, port, EchoClientFactory())
reactor.run()

注意:clientConnectionFailed在不能建立连接时被调用,clientConnectionLost在创建连接后被释放时调用。

重新连接

很多时候,由于网络错误,客户端连接就会丢失意义。有一种方法,在连接中断时,disconnection会调用connector.connect()来重新进行连接:

from twisted.internet.protocol import ClientFactory

class EchoClientFactory(ClientFactory):
	def clientConnectionLost(self, connector, reason):
		connector.connect()

作为第一个参数传递的connector是connection和protocol的接口,当连接失败,factory收到clientConnectionLost事件时,factory可以调用connector.connect()来从头开始建立连接。

然而,大部分需要这个功能的程序应该是实现ReconnectingClientFactory,它在连接中断或失败时尝试重新连接,不断的延时并不断尝试重新连接。

下面是实现了ReconnectingClientFactory的Echo协议:

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)

2.2.5 高级示例: ircLogBot

ircLogBot简介

到现在的客户端还相当简单,更复杂的示例在Twisted文档的doc/examples目录下面。

from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.python import log

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))


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 mede
    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服务器,调入一个频道并将所以流量记录到文件中,它表明了连接中断时重新连接的连接层逻辑,以及Factory中的持久数据。

Factory中的持久数据

由于Protocol实例在每次连接时都会重新创建,客户端需要一些方法来跟踪应该被持久化的数据。在这个logging bot案例中,它需要知道哪个频道被记录及记录在哪里。

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 

当protocol被创建,它就会得到factory的引用赋值给self.factory,它可以通过自身的逻辑对factory的属性进行访问。在LogBot案例中,它打开文件,并连接factory中的通道存储。

2.2.6 延伸

本书中所使用的Protocol类是实现了IProtocol接口的基本类,该接口在大多少Twisted应用都广泛应用。要了解完整的IProtocol接口,请参阅API文档。

本书中在一些示例中使用的transport属性提供了ITCPTransport接口,要了解有关该接口的详细情况,参阅ITCPTransport的API文档。

接口是用于指定一个类所拥有的或如何使用的方法和属性的。参阅Components:Interfaces和Adapters(文档148页)。

你不知道的淘宝66种生活

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值