3.Twisted TCP Socket 客户端编程

3.Twisted TCP Socket 客户端编程

Twisted 是一个设计得非常灵活的框架 ,同样也提供了基于 Protocol 类的 TCP 客户端编程方法。 我们实际实现协议解析和处理的地方是 Protocol 类。此类通常来自 twisted.internet.protocol.Protocol。大多数协议处理程序要么继承自此类,要么继承自它的子类之一。连接到服务器时将实例化协议类的实例,并在连接完成后消失。这意味着持久配置不会保存在 Protocol。

持久配置应该保存在一个 Factory 类中,该类通常继承自 twisted.internet.protocol.Factory(或twisted.internet.protocol.ClientFactory)。默认 Factory 类只是实例化 Protocol,然后将协议的 factory 属性设置为指向自身(Factory )。这允许 Protocol 访问并可能修改持久配置。

3.1 Protocol

Protocol 和辅助类、函数是大部分代码所在的位置。 Twisted 协议以异步方式处理数据。这意味着协议从不等待事件,而是在事件从网络到达时对其进行响应。 Protocol 类的事件与服务端相同:

  • connectionMade
  • connectionLost
  • dataReceived

如:

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
")
        self.transport.loseConnection()

该协议连接到服务器,向其发送欢迎消息,然后终止连接。

connectionMade 事件通常发生在对象的设置以及 Protocol 任何初始问候(如上面的 WelcomeMessage 协议中)。Protocol 任何特定对象的析构都在 connectionLost 事件中。

3.1.1 一次性客户端

很多情况下,我们的协议只需要连接服务器一次,代码只想得到一个连接的协议实例。在这些情况下我们可以使用 twisted.internet.endpoints 提供的适当的 API,特别 connectProtocol 是采用协议实例而不是 Factory。

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
" % 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()

无论客户端端点的类型如何,建立新连接的方法都是简单地将其 connectProtocol 与协议实例一起传递。这意味着很容易更改我们用于连接的机制,而无需更改程序的其余部分。例如,要通过 SSL 运行 greeter 示例,唯一需要的更改是实例化 SSL4ClientEndpoint 而不是 TCP4ClientEndpoint。为了利用这一点,启动新连接的函数和方法通常应该接受一个端点作为参数并让调用者构造它,而不是接受像“主机”和“端口”这样的参数并构造它自己的。

3.2 ClientFactory

虽然有更简单的方法可以实现客户端连接,但我们在实际开发中仍然会使用较低级别的 API ,这是因为有一些重要的功能 (例如自动重新连接) 在端点中并没有实现。

要使用较低级别的连接 API,我们需要直接调用 reactor.connect 方法之一。对于这些情况,我们需要一个ClientFactory。ClientFactory 负责创建 Protocol 并接收与连接状态相关的事件。这允许它在发生连接错误时执行诸如重新连接之类的操作。如:

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)

ClientFactory 类的事件:

  • startedConnecting - 开始连接到服务端时触发
  • clientConnectionLost - 客户端连接断开时触发
  • clientConnectionFailed - 客户端连接失败时触发

要将 EchoClientFactory 连接到服务器,可以使用以下代码:

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

3.3 Reactor Client APIs

3.3.1 连接 TCP

IReactorTCP.connectTCP 提供对 IPv4 和 IPv6 TCP 客户端的支持。它接受如下参数:

  • host - 可以是主机名或 IP 地址

如果是主机名,reactor 会在尝试连接之前自动将该名称解析为 IP 地址。这意味着对于具有多个地址记录的主机名,重新连接尝试可能并不总是转到同一台服务器。这也意味着每次连接尝试都有名称解析开销。如果我创建的是许多短连接(通常大约每秒数百或数千),那么我们可能希望先将主机名解析为地址,然后将地址传递给connectTCP。

  • port - 端口

3.3.2 重新连接

通常,由于网络问题,客户端的连接会无意中丢失。断开连接后重新连接的一种方法是 connector.connect() 在连接丢失时调用:

from twisted.internet.protocol import ClientFactory

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

作为第一个参数传递的连接器是连接和协议之间的接口。当连接失败并且 Factory 接收到 clientConnectionLost 事件时,Factory 可以调用 connector.connect() 来从头开始重新连接。

但是,大多数需要此功能的程序应该使用 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)

3.4 Factory 中的持久数据

由于 Protocol 每次建立连接时都会重新创建实例,因此客户端需要某种方式来跟踪应该保留的数据。比如,对于日志记录机器人,它需要知道它正在记录哪个通道,以及在哪里记录它。 代码如下:

from twisted.words.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.logger.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

创建协议时,它会获得对 Factory 的引用,即 self.factory。 然后它可以在其逻辑中访问 Factory 的属性。如上面代码中的 self.factory.channel。

Factory 有一个默认的实现 buildProtocol。它与上面的示例使用 protocol Factory 的属性创建协议实例所做的事情相同。在上面的示例中,工厂可以重写为如下所示:

class LogBotFactory(protocol.ClientFactory):
    protocol = LogBot

    def __init__(self, channel, filename):
        self.channel = channel
        self.filename = filename



发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章