2.Twisted TCP Socket 服务端编程

2.Twisted TCP Socket 服务端编程

使用 Twisted 进行 TCP Socket 编程时,不需要我们操作 Socket 的 bind、send、recieve 等基本原语,直接针对 Twisted 的 Protocol、Factory 等类进行编程,定义其子类并重新实现 connectionMade、dataRecieved 等方法进行事件处理即可。本节我们来学习 如何使用 Twisted 实现 TCP 服务器的网络协议解析和处理(相同的代码可以重用于 SSL 和 Unix 套接字服务器)。

协议处理类通常是子类 twisted.internet.protocol.Protocol。大多数协议处理程序要么继承于该类,要么继承于该类的子类之一。协议类的实例是按连接按需实例化的,并在连接完成后消失。也就是说持久配置不会保存在 Protocol 中。

持久化配置保存在一个称为 Factory 的类中,该类通常继承自 twisted.internet.protocol.Factory 的 buildProtocol 方法,用于为每个新连接 Factory 创建一个 Protocol 。

能够在多个端口或网络地址上提供相同的服务通常很有用。这就是为什么 Factory 不监听连接,实际上对网络一无所知。

2.1 Protocols

Twisted 协议以异步方式处理数据。协议在事件从网络到达时对其进行响应,并且事件作为对协议方法的调用而到达。 如:

from twisted.internet.protocol import Protocol

class Echo(Protocol):

    def dataReceived(self, data):
        self.transport.write(data)

该协议只是简单地写回写入它的任何内容,而不响应所有事件。

Protocol 类的事件:

  • connectionMade

该事件在连接建立时由 Twisted 框架调用, 该方法的主要作用通常是在系统中注册该连接,便于以后使用。

  • connectionLost

当连接断开时由 Twisted 框架调用,该方法通常用来清理连接所占用的资源。

  • dataReceived

当收到客户端发送的数据时由 Twisted 框架调用,通常具体的业务处理逻辑就是在这里实现。

举例如下:

from twisted.internet.protocol import Protocol

class Echo(Protocol):

    def __init__(self, factory):
        self.factory = factory

    def connectionMade(self):
        self.factory.numProtocols = self.factory.numProtocols + 1
        self.transport.write(
            "Welcome! There are currently %d open connections.\n" %
            (self.factory.numProtocols,))

    def connectionLost(self, reason):
        self.factory.numProtocols = self.factory.numProtocols - 1

    def dataReceived(self, data):
        self.transport.write(data)

在上面的例子中,connectionMade 与 connectionLost 合作以保持共享对象(Factory)中活动协议的计数。该类 Echo 的构造方法创建新实例时必须传递 Factory 参数。Factory 用于共享任何给定连接生命周期的状态。

在 Protocol 子类的实现中,我们要给客户端连接写入数据,采用 self.transport.write 方法;对于 transport ,我们可以理解为数据传输或者交易,transport 还具有两个方法:loseConnection() and abortConnection()。

  • loseConnection

服务端主动与客户端断开连接,如果 loseConnection 在写入传输后立即调用,仅当 Twisted 将所有数据写入操作系统时,该 loseConnection 调用才会关闭连接,因此在这种情况下使用它是安全的,而不必担心传输写入丢失。

  • abortConnection

在某些情况下,等到所有数据都写完并不是我们想要的。由于网络故障,或连接另一端的错误或恶意,写入传输的数据可能无法交付,因为即使 loseConnection 调用连接也不会丢失。在这些情况下,abortConnection 可以使用:它立即关闭连接,而不管传输中是否有未写入的缓冲数据或仍然注册的生产者。

该方法仅在 Twisted 11.1 及更高版本中可用。

2.2 使用 Protocol

对于编写好的 Protocol 子类,如何使用呢?下面来看一段代码:

from twisted.internet.protocol import Factory
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet import reactor

class EchoFactory(Factory):
    def __init__(self):
        self.numProtocols = 0
    
    def buildProtocol(self, addr):
        return Echo()

# 8007 is the port you want to run under. Choose something >1024
endpoint = TCP4ServerEndpoint(reactor, 8007)
endpoint.listen(EchoFactory())
reactor.run()

在这个例子中,我创建了一个协议 Factory。在该 Factory 中,它的工作是构建 Echo 协议实例,所以我将它的buildProtocol 方法设置为返回 Echo 类的实例。接下来,监听一个 TCP 端口,所以我创建一个TCP4ServerEndpoint 来识别我想要绑定到的端口,然后将我刚刚创建的工厂传递给它的 listen方法。

endpoint.listen() 告诉反应器使用特定协议处理与端点地址的连接,但反应器需要运行才能执行操作。 reactor.run() 启动反应器,然后永远等待连接到达您指定的端口。您可以通过在终端中按 Control-C 或调用来停止反应器 reactor.stop() 来退出服务端程序。

2.3 Factories

在 Twisted 中,Factory 子类对 Protocol 子类进行管理,当有新的客户端连接时,Twisted 框架调用 Factory 子类的 buildProtocol() 方法,从而创建 Protocol 子类的实例,如上面的例子。

2.3.1 更简单的协议创建

对于简单地实例化特定协议类的实例的 Factory,有一种更简单的方法来实现。该 buildProtocol 方法的默认实现调用 protocol factory 的属性来创建一个 Protocol 实例,然后在其上设置一个属性,称为 factory 指向 factory 本身。这允许每次 Protocol 访问并可能修改持久配置。如下面的示例:

from twisted.internet.protocol import Factory, Protocol
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet import reactor

class QOTD(Protocol):

    def connectionMade(self):
        # self.factory was set by the factory's default buildProtocol:
        self.transport.write(self.factory.quote + '\r\n')
        self.transport.loseConnection()


class QOTDFactory(Factory):

    # This will be used by the default buildProtocol to create new protocols:
    protocol = QOTD

    def __init__(self, quote=None):
        self.quote = quote or 'An apple a day keeps the doctor away'

endpoint = TCP4ServerEndpoint(reactor, 8007)
endpoint.listen(QOTDFactory("configurable quote"))
reactor.run()

如果我们只需要一个简单的 Factory 来构建协议而无需任何额外行为,那么 Twisted 13.1 添加的Factory.forProtocol 是一种更简单的方法。 如:

class QOTDFactory(Factory):

    Factory.forProtocol(QOTD)

    def __init__(self, quote=None):
        self.quote = quote or 'An apple a day keeps the doctor away'

2.4 服务端编程示例

接下来,我们编写一个简单的聊天服务器,该聊天服务器允许用户选择用户名,然后与其他用户通信。该程序演示了工厂中共享状态的使用、每个单独协议的状态机以及不同协议之间的通信。

from twisted.internet import reactor
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver


class Chat(LineReceiver):
    def __init__(self, users):
        self.users = users
        self.name = None
        self.state = "GETNAME"

    def connectionMade(self):
        self.sendLine("What's your name?")

    def connectionLost(self, reason):
        if self.name in self.users:
            del self.users[self.name]

    def lineReceived(self, line):
        if self.state == "GETNAME":
            self.handle_GETNAME(line)
        else:
            self.handle_CHAT(line)

    def handle_GETNAME(self, name):
        if name in self.users:
            self.sendLine("Name taken, please choose another.")
            return
        self.sendLine(f"Welcome, {name}!")
        self.name = name
        self.users[name] = self
        self.state = "CHAT"

    def handle_CHAT(self, message):
        message = f"<{self.name}> {message}"
        for name, protocol in self.users.iteritems():
            if protocol != self:
                protocol.sendLine(message)


class ChatFactory(Factory):
    def __init__(self):
        self.users = {}  # maps user names to Chat instances

    def buildProtocol(self, addr):
        return Chat(self.users)


reactor.listenTCP(8123, ChatFactory())
reactor.run()



原文链接:,转发请注明来源!