如何限制Twisted中的同时连接数

时间:2015-05-12 16:08:20

标签: python python-2.7 asynchronous twisted twisted.internet

所以我建立了一个扭曲的服务器,我想知道限制同时连接数量的最佳方法是什么?

让我的工厂返回没有最好的方法吗?当我这样做时,我抛出了很多例外:

exceptions.AttributeError: 'NoneType' object has no attribute 'makeConnection'

我想让客户端只是坐在队列中,直到当前连接数回落,但我不知道如何异步执行。

目前我正在使用我的工厂这样做:

class HandleClientFactory(Factory):

    def __init__(self):
            self.numConnections = 0 

    def buildProtocol(self, addr):
            #limit connection number here
            if self.numConnections >= Max_Clients:
                    logging.warning("Reached maximum Client connections")
                    return None

            return HandleClient(self)

可以工作,但断开连接而不是等待,并且还会抛出许多未处理的错误。

1 个答案:

答案 0 :(得分:5)

你必须自己建造它。幸运的是,这些碎片大部分都是这样做的(你可能会要求稍微更合适的碎片但是......)

首先,要避免使用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

PauseTransportDoesNothing类似,但与次要(且有用)不同之处在于,只要它连接到传输,就会告诉传输暂停。因此,不会从连接中读取任何数据,只要您准备好处理它,它们都将保持缓冲状态。

对于下一个要求,存在许多可能的解决方案。其中一个最简单的方法是将工厂用作存储:

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

现在,传输要调用方法的对象已将其方法替换为来自希望调用方法的对象的方法。再次,这显然是一个黑客。如果你愿意的话,你可以把不太讨厌的东西放在一起(例如,明确支持委托给另一个协议的第三个协议类)。这个想法是一样的 - 它只会在你的键盘上磨损。对于它的价值,我怀疑使用Tubes做类似的事情可能更容易,也更少打字但我现在会尝试基于该库的解决方案。

我已经避免解决让currentConnections保持最新状态的问题。由于您在问题中已经有numConnections,我假设您知道如何管理该部分。我在这里的最后一步所做的就是假设您执行减量步骤的方法是在工厂调用oneConnectionDisconnected

我还避免解决排队连接变得无聊并消失的事件。这将主要按写入方式工作 - 在您调用resumeProducing之后,Twisted将不会注意到连接已关闭,然后将在您的应用程序协议上调用connectionLost。这应该没问题,因为您的协议无论如何都需要处理丢失的连接。