Twisted 综述

…衆ロ難τιáo~ 提交于 2020-02-12 08:13:51

Twisted 框架概况

  Twisted 是一个有着10多年历史的开源事件驱动框架。Twisted 支持很多协议,包括传输层的TCP、UDP、TLS,以及应用层的HTTP、FTP等。对所有这些协议,Twisted提供了

客户端和服务器方面的开发工具。

  Twisted 是一个高性能的编程框架。在不同的操作系统平台上,Twisted 利用不同的底层技术实现了高效能通信。在 Windows 中,Twisted 的实现基于 I/O 完成端口IOCPInput/Output Completion Port) 技术,它保证了底层高效地将 I/O 事件通知给框架及应用程序。在 Linux 中,Twisted 的实现基于 epoll 技术, epoll 是 Linux 下多路复用 I/O 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

  在开发技术上,Twisted 引导程序员使用异步编程模型。Twisted 提供了丰富的 DeferThreading等特性来支持异步编程。

在 Linux 与 macOS 中 安装 Twisted

  Twisted在安装过程中需要先在设备上编译,因此在安装之前需要确保安装了Python编译开发包。该步骤在不同的操作系统略有不同,以Ubuntu Linux 举例:

# apt-get  install  python3-dev            // 安装 Python3 开发包
# pip  install  twisted                    // 安装 Twisted

  而在macOS系统的brew安装工具中自带了Python开发包,因此可以直接使用上述第二条命令(pip)安装。

  安装完成后可以用如下命令查看Twisted版本:

# pip freeze | grep Twisted
Twisted==19.2.1

举个例子: 开发TCP广播系统

  该广播系统接受任意客户端的链接请求,并且将任意客户端发给服务器的消息转发给所有其他客户端。本系统是一个基本的实时通信模型。

  使用 Twisted 进行基于传输层TCP的编程时,无须程序员操作Socket bindsendreceive等基本原语;而是直接针对TwistedProtocolFactory 等类进行编程,定义它们的子类并重写connectionMadedataReceived进行事件化的TCP编程风格。

1、开发 Protocol 子类

  针对每个客户端连接,Twisted 框架建立了一个Protocol子类的实例管理该连接。开发者需要编写该子类,使其能够处理3个基本事件响应函数。

  • connectionMade(): 当连接建立时由 Twisted 框架调用。在实际应用中,本函数的主要作用常常是在系统中注册该连接,方便以后使用。
  • dataReceived():      当收到客户端的数据时由 Twisted 框架调用。
  • connectionLost():  当连接断开时由 Twisted 框架调用。在实际应用中,本函数常常用来清理连接占用的资源。
from twisted.internet.protocol import Protocol
import random
import string

clients = []


class Spreader(Protocol):

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

    def connectionMade(self):
        self.factory.numPortocols += 1
        self.client_id = ''.join(random.sample(string.ascii_letters + string.digits, 8)).lower()
        print("new connect: %d" % self.factory.numPortocols)
        self.transport.write(
            (u"欢迎来到 Twisted World, 您是第 %d 个客户端用户!\n" % self.factory.numPortocols).encode())

        clients.append(self)

    def connectionLost(self, reason):
        clients.remove(self)
        print("lost connect: %s" % self.client_id)

    def dataReceived(self, data):
        if data == "close":
            self.transport.loseConnection()
            print("%s closed " % self.client_id)
        else:
            print("spreading message from %s % s" % (self.client_id, data))
            for client in clients:
                if client != self:
                    client.transport.write(data)

代码解析如下:

  • 全局列表变量 clients 保存所有的客户端的连接 (即 Protocol 子类 Spreader 的实例)。
  • 定义 Protocol 的子类 Spreader, 在其中实现需要重写的方法。
  • connectionMade() 中对连接的客户端进行计数,并将 self 保存到 clients 列表中。
  • connectionLost() 中执行与 connectionMade() 函数相反的操作。
  • dataReceived() 中轮询当前 clients 列表中的所有客户端,将收到的数据通过 Protocol.transport.write() 函数分发给除自己之外的所有客户端。
  • 如果收到客户端发来的数据 “close",则调用 Protocol.transport.loseConnection() 主动关闭与客户端的连接。

2、开发 Factory 子类

  Twisted 中的 Factory 子类起到对 Protocol类的管理作用,当有新的客户端连接时,框架调用 Factory.buildProtocol(),使得程序员可以在这里创建 Protocol 子类的实例。Factory 子类及服务启动程序的代码如下:

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


class SpreadFactory(Factory):

    def __init__(self):
        self.numPortocols = 0

    def buildProtocol(self, addr):
        return Spreader(self)


if __name__ == "__main__":

    # 8006 是本服务器的监听端口, 建议选择大于1024 的端口
    endpoint = TCP4ServerEndpoint(reactor, 8006)
    endpoint.listen(SpreadFactory())
    # 挂起运行
    reactor.run()

  建立 Factory 的子类 SpreadFactory,在其中只需要重写两个函数:在 __init__ 中将客户端计数器 self.numProtocols 置 0;在buildProtocol() 中建立 Protocol 子类 Spreader 的实例。

  通过 TCP4ServerEndpoint() 定义服务器的监听端口,并用 listen() 函数指定该端口所绑定的 Factory 子类实例,运行 twisted.internet.reactor.run() 可启动服务器。

3、广播客户端

  Twisted 同样提供了基于 Protocol 类的 TCP 客户端的编程方法。实现一个与服务器程序相匹配的 TCP 客户端程序。

from twisted.internet.protocol import Protocol, ClientFactory
from twisted.internet import reactor
import sys
from datetime import datetime


class Echo(Protocol):
    def connectionMade(self):
        print("Connected to the server!")

    def dataReceived(self, data):
        print("got messages: ", data.decode())
        reactor.callLater(5, self.say_hello)

    def connectionLost(self, reason):
        print("Disconnected from the server!")

    def say_hello(self):
        if self.transport.connected:
            self.transport.write((u"hello, I'm %s %s" % (sys.argv[1], datetime.now())).encode())


class EchoClientFactory(ClientFactory):

    def __init__(self):
        self.protocol = None

    def startedConnecting(self, connector):
        print("started to connect.")

    def buildProtocol(self, addr):
        self.protocol = Echo()
        return self.protocol

    def clientConnectionLost(self, connector, reason):
        print("Lost connection. Reason:", reason)

    def clientConnectionFailed(self, connector, reason):
        print("Connection failed. Reason:", reason)


if __name__ == "__main__":
    host = "127.0.0.1"
    port = 8006
    factory = EchoClientFactory()
    reactor.connectTCP(host, port, factory)
    reactor.run()

  解析如下:

  • 与服务端类似,使用 Protocol 管理连接,其中可重载的函数 connectionMade()dataReceived()connectionLost() 等含义与服务器中含义相同。
  • 定义 ClientFactory 的子类 EchoClientFactory, 用于构造 Protocol 子类 Echo。ClientFactory 继承自 Factory 类,这里重写了它的3个事件响应函数,即 startedConnection() 函数在连接建立时被调用;clientConnectionLost() 函数在连接断开时被调用;clientConnectionFailed() 函数在连接建立失败时被调用。
  • Echo.dataReceived() 函数中,每次接收到消息后用 reactor.callLater() 函数延迟调用 say_hello() 函数。
  • 在 say_hello() 函数中使用 self.transport.connected 属性判断当前是否处于连接状态。如果是则调用 self.transport.write() 函数向服务器发送消息。
  • twisted.internet.reactor.connectTCP() 函数用于指定要连接的服务器地址和端口,然后仍然要调用 twisted.internet.reactor.run() 函数启动事件循环。

  为了更好地观察本例中 Echo(Protocol 的子类)与 EchoClientFactory( ClientFactory 的子类) 两个类之间回调事件函数的执行顺序,现在打开三个命令行控制台,分别执行一个服务器程序和两个客户端程序,比如:

# python server.py                   // 服务器程序
# python client.py    Alice          // 客户端程序
# python client.py    Bob            // 客户端程序 

  运行若干秒后用 CTRL-C 终止服务器程序的执行。观察服务器程序:

new connect: 1
spreading message from zjqaohpd b"hello, I'm Alice 2019-06-18 21:02:57.807600"
new connect: 2
spreading message from v0f4fhl3 b"hello, I'm Bob 2019-06-18 21:03:13.586417"
spreading message from zjqaohpd b"hello, I'm Alice 2019-06-18 21:03:18.593345"
spreading message from v0f4fhl3 b"hello, I'm Bob 2019-06-18 21:03:23.600063"
spreading message from zjqaohpd b"hello, I'm Alice 2019-06-18 21:03:28.604293"
lost connect: v0f4fhl3
lost connect: zjqaohpd

  观察 Alice 客户端:

started to connect.
Connected to the server!
got messages:  欢迎来到 Twisted World, 您是第 1 个客户端用户!

got messages:  hello, I'm Bob 2019-06-18 21:03:13.586417
got messages:  hello, I'm Bob 2019-06-18 21:03:23.600063
Disconnected from the server!
Lost connection. Reason: [Failure instance: Traceback (failure with no frames):
     <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]

  结合服务器程序、客户端程序中的代码,在连接建立与关闭时回调事件函数的执行顺序如下:

  • 建立连接
  1. ClientFactory.startedConnecting()
  2. Protocol.connectionMade()
  • 已连接
  1. Protocol.dataReceived() 接收消息;
  2. Protocol.transport.write() 发送消息。
  • 连接断开:
  1. Protocol.connectionLost()
  2. ClientFactory.clientConnectionLost()

  即 建立连接时先执行 ClientFactory 中的回调,然后执行 Protocol 中的回调,而连接断开时则正好相反。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!