正确使用客户端并延迟使用Twisted

时间:2013-07-17 08:56:38

标签: python twisted deferred

我使用socket实现了一个基本的SOCKS4客户端,但我的Twisted转换并不是很顺利。这是我目前的代码:

import struct
import socket

from twisted.python.failure import Failure

from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.internet.protocol import Protocol, ClientFactory

class Socks4Client(Protocol):
    VERSION = 4
    HOST = "0.0.0.0"
    PORT = 80

    REQUESTS = {
        "CONNECT": 1,
        "BIND": 2
    }

    RESPONSES = {
        90: "request granted",
        91: "request rejected or failed",
        92: "request rejected because SOCKS server cannot connect to identd on the client",
        93: "request rejected because the client program and identd report different user-ids"
    }

    def __init__(self):
        self.buffer = ""

    def connectionMade(self):
        self.connect(self.HOST, self.PORT)

    def dataReceived(self, data):
        self.buffer += data

        if len(self.buffer) == 8:
            self.validateResponse(self.buffer)

    def connect(self, host, port):
        data = struct.pack("!BBH", self.VERSION, self.REQUESTS["CONNECT"], port)
        data += socket.inet_aton(host)
        data += "\x00"

        self.transport.write(data)

    def validateResponse(self, data):
        version, result_code = struct.unpack("!BB", data[1:3])

        if version != 4:
            self.factory.protocolError(Exception("invalid version"))
        elif result_code == 90:
            self.factory.deferred.callback(self.responses[result_code])
        elif result_code in self.RESPONSES:
            self.factory.protocolError(Exception(self.responses[result_code]))
        else:
            self.factory.protocolError(Exception())

        self.transport.abortConnection()

class Socks4Factory(ClientFactory):
    protocol = Socks4Client

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

    def clientConnectionFailed(self, connector, reason):
        self.deferred.errback(reason)

    def clientConnectionLost(self, connector, reason):
        print "Connection lost:", reason

    def protocolError(self, reason):
        self.deferred.errback(reason)

def result(result):
    print "Success:", result

def error(reason):
    print "Error:", reason

if __name__ == "__main__":
    d = Deferred()
    d.addCallbacks(result, error)

    factory = Socks4Factory(d)
    reactor.connectTCP('127.0.0.1', 1080, factory)

    reactor.run()
  1. 我有一种感觉,我在滥用Deferred。这是从我的客户发送结果的正确方法吗?
  2. 我已经阅读了一些教程,查看了文档,并阅读了与Twisted捆绑在一起的大多数协议,但我仍然无法弄清楚:ClientFactory究竟是什么?我用正确的方式使用它吗?
  3. clientConnectionLost被触发了很多。有时我会失去联系并获得成功的回复。怎么会这样?这意味着什么,我应该将其视为错误吗?
  4. 如何确保我的延迟调用只有一个回调/错误回复?
  5. 任何提示都表示赞赏。

1 个答案:

答案 0 :(得分:5)

  

我有一种感觉,我在滥用延迟。这是从我的客户端发送结果的正确方法吗?

这不理想,但也不是完全错误的。通常,您应该尽量使实例化Deferred的代码尽可能接近在Deferred.callback上调用Deferred.errbackDeferred的代码。在这种情况下,这些代码片段相距很远 - 前者位于__main__,而后者位于由__main__中的代码创建的类创建的类中。这有点像Demeter的规律 - 这两件事之间的步骤越多,软件的耦合越紧密,越不灵活,越脆弱。

考虑为Socks4Client提供一个创建并返回此Deferred实例的方法。然后,尝试使用端点设置连接,以便您可以更轻松地调用此方法:

endpoint = TCP4StreamClientEndpoint(reactor, "127.0.0.1", 1080)
d = endpoint.connect(factory)
def connected(protocol):
    return protocol.waitForWhatever()
d.addCallback(connected)
d.addCallbacks(result, error)

这里要注意的一点是使用端点,不会调用工厂的clientConnectionFailedclientConnectionLost方法。端点接管前者(尽管不是后者)。

  

我已经阅读了一些教程,查看了文档,并阅读了与Twisted捆绑在一起的大多数协议,但我仍然无法弄清楚:ClientFactory究竟是什么?我是以正确的方式使用它吗?

这就是你正在做的事情。 :)它创建用于连接的协议实例。需要工厂,因为您可能会创建与许多服务器(或与一个服务器的多个连接)的连接。但是,很多人都遇到了ClientFactory的问题,因此最近推出的Twisted API并不依赖它。例如,您还可以将连接设置为:

endpoint = TCP4StreamClientEndpoint(reactor, "127.0.0.1", 1080)
d = connectProtocol(endpoint, Socks4Client())
...

ClientFactory现在不在了。

  

clientConnectionLosts被触发了很多。有时我会失去联系并获得成功的回复。怎么会这样?这意味着什么,我应该将其视为错误吗?

每个连接最终都必须丢失。您必须自己决定这是否是错误。如果你已经完成了你想要做的所有事情并且你打电话给loseConnection,那么这可能不是一个错误。考虑与HTTP服务器的连接。如果您已发送请求并收到回复,则丢失连接可能不是什么大问题。但如果你只收到一半的回复,那就是问题。

  

如何确保我的延迟调用只有一个回调/错误回复?

如果按照我上面的第一个问题的描述构建代码,那么执行此操作会变得更容易。当在Deferred上使用回调/错误的代码遍布程序的大部分时,那么正确地执行此操作变得更加困难。

但这只是适当的状态跟踪问题。一旦你推迟了结果,你必须安排知道你不应该给它另一个。一个常见的习惯用法是删除对Deferred的引用。例如,如果要将其保存为协议实例上的属性值,则在给出Deferred其结果时将该属性设置为None