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