您必须自己构建它。幸运的是,这些部件大部分都准备好了(你可能会要求更合适的部件,但是......)
首先,为了避免AttributeError
(这确实会导致连接关闭),请务必IProtocol
从您的buildProtocol
方法中返回一个提供程序。
class DoesNothing(Protocol):
pass
class YourFactory(Factory):
def buildProtocol(self, addr):
if self.currentConnections < self.maxConnections:
return Factory.buildProtocol(self, addr)
protocol = DoesNothing()
protocol.factory = self
return protocol
如果您使用这个工厂(填写缺失的部分 - 例如,正确初始化maxConnections
和跟踪currentConnections
),那么您会发现一旦达到限制就连接的客户端获得了DoesNothing
协议。他们可以向该协议发送尽可能多的数据。它将全部丢弃。它永远不会向他们发送任何数据。它将保持连接打开,直到他们关闭它。简而言之,它什么也不做。
但是,您还希望客户端在连接数低于限制时实际接收到服务。
要做到这一点,你需要更多的部分:
- 您必须保留他们可能发送的任何数据缓冲,以便在您准备好读取它时可以读取它。
- 您必须跟踪连接,以便在时机成熟时开始为它们提供服务。
- 您必须在上述时间开始为他们服务。
对于其中的第一个,您可以使用大多数传输的功能来“暂停”:
class PauseTransport(Protocol):
def makeConnection(self, transport):
transport.pauseProducing()
class YourFactory(Factory):
def buildProtocol(self, addr):
if self.currentConnections < self.maxConnections:
return Factory.buildProtocol(self, addr)
protocol = PauseTransport()
protocol.factory = self
return protocol
PauseTransport
类似于DoesNothing
但有一点(和有用)的区别,即一旦连接到传输,它就会告诉传输暂停。因此,永远不会从连接中读取任何数据,并且无论何时您准备好处理它,它都会保持缓冲状态。
对于下一个需求,存在许多可能的解决方案。最简单的一种是将工厂用作存储:
class PauseAndStoreTransport(Protocol):
def makeConnection(self, transport):
transport.pauseProducing()
self.factory.addPausedTransport(transport)
class YourFactory(Factory):
def buildProtocol(self, addr):
# As above
...
def addPausedTransport(self, transport):
self.transports.append(transport)
同样,通过正确的设置(例如,初始化transports
属性),您现在拥有所有传输的列表,这些传输对应于您已接受的高于并发限制的正在等待服务的连接。
对于最后一个要求,所需要的只是实例化和初始化实际上能够为您的客户服务的协议。实例化很容易(这是您的协议,您可能知道它是如何工作的)。初始化主要是调用makeConnection
方法:
class YourFactory(Factory):
def buildProtocol(self, addr):
# As above
...
def addPausedTransport(self, transport):
# As above
...
def oneConnectionDisconnected(self)
self.currentConnections -= 1
if self.currentConnections < self.maxConnections:
transport = self.transports.pop(0)
protocol = self.buildProtocol(address)
protocol.makeConnection(transport)
transport.resumeProducing()
我省略了跟踪address
所需参数的细节buildProtocol
(transport
从它的原点带到程序的这一部分,如果你的程序真的想要它,应该清楚如何对原始地址值做类似的事情)。
除此之外,这里发生的所有事情都是您采用下一个排队传输(如果需要,您可以使用不同的调度算法,例如 LIFO)并将其连接到您选择的协议,就像 Twisted 所做的那样。最后,撤消先前的暂停操作,以便数据开始流动。
或者……几乎。这将非常巧妙,除非 Twisted 传输实际上并没有公开任何方式来更改它们将数据传递到哪个协议。因此,正如所写,来自客户端的数据实际上将被传递到原始PauseAndStoreTransport
协议实例。您可以解决这个问题(“hack”显然是正确的词)。将传输和 PauseAndStoreTransport
实例存储在工厂的列表中,然后:
def oneConnectionDisconnected(self)
self.currentConnections -= 1
if self.currentConnections < self.maxConnections:
originalProtocol, transport = self.transports.pop(0)
newProtocol = self.buildProtocol(address)
originalProtocol.dataReceived = newProtocol.dataReceived
originalProtocol.connectionLost = newProtocol.connectionLost
newProtocol.makeConnection(transport)
transport.resumeProducing()
现在,传输要调用方法的对象已将其方法替换为您希望调用方法的对象中的方法。同样,这显然是一个 hack。如果您愿意,您可能可以将一些不那么骇人听闻的东西放在一起(例如,明确支持委托给另一个协议的第三个协议类)。这个想法是一样的——它只会在你的键盘上磨损更多。对于它的价值,我怀疑使用Tubes做类似的事情可能更容易而且打字更少,但我暂时将尝试基于该库的解决方案留给其他人。
我避免解决保持currentConnections
正确更新的问题。既然您已经numConnections
提出了问题,我假设您知道如何管理该部分。我在最后一步所做的只是假设您执行递减步骤的方式是调用oneConnectionDisconnected
工厂。
我还避免解决排队连接变得无聊并消失的事件。这将主要按书面方式工作 - Twisted 不会注意到连接已关闭,直到您调用resumeProducing
然后connectionLost
将在您的应用程序协议上调用。这应该没问题,因为您的协议无论如何都需要处理丢失的连接。