如何正确触发python扭曲运输?

时间:2012-05-09 19:14:45

标签: python twisted

我被要求编写一个连接到服务器的类,异步发送服务器各种命令,然后将返回的数据提供给客户端。我被要求用Python做这个,这对我来说是一种新语言。我开始挖掘并找到Twisted框架,它提供了一些非常好的抽象(Protocol,ProtocolFactory,Reactor),它可以完成很多我必须要做的事情,如果我要推出自己的基于套接字的应用程序。考虑到我必须解决的问题,这似乎是正确的选择。

我已经浏览了网上的大量示例(主要是Krondo),但我还没有看到一个很好的例子,创建一个将通过网络发送多个命令的客户端,并保持连接我创建。在这种情况下,服务器(我无法控制)在发送响应后不会断开连接。那么,设计客户端的正确方法是什么,以便我可以通过各种方式为服务器添加功能?

现在我这样做:

class TestProtocol(Protocol)
    def connectionMade(self):
         self.transport.write(self.factory.message)

class TestProtocolFactory(Factory):
    message = ''
    def setMessage(self, msg):
        self.message = msg

def main():
    f = TestProtocolFactory()
    f.setMessage("my message")
    reactor.connectTCP(...)
    reactor.run()

我真正想做的是通过reactor调用self.transport.write(...)(实际上,从另一个执行线程按需调用TestProtocolFactory :: setMessage()),而不仅仅是在建立连接时。

3 个答案:

答案 0 :(得分:4)

取决于。以下是一些可能性:

我假设

方法1.您有一个发送服务器的命令列表,由于某种原因不能一次完成所有这些操作。在这种情况下,当前一个答案返回时发送一个新的:

class proto(parentProtocol):
    def stringReceived(self, data):
        self.handle_server_response(data)
        next_command = self.command_queue.pop()
        # do stuff

方法2.您发送给服务器的内容取决于服务器发送给您的内容:

class proto(parentProtocol):
    def stringReceived(self, data):
        if data == "this":
            self.sendString("that")
        elif data == "foo":
            self.sendString("bar")
        # and so on

方法3.您不关心服务器发送的内容,您只想定期发送一些命令:

class proto(parentProtocol):
    def callback(self):
        next_command = self.command_queue.pop()
        # do stuff
    def connectionMade(self):
        from twisted.internet import task
        self.task_id = task.LoopingCall(self.callback)
        self.task_id.start(1.0)

方法4:您的编辑现在提到从另一个线程触发。请随意查看扭曲的文档,以确定proto.sendString是否为线程安全。你可以直接打电话,但我不知道。方法3 线程安全的。只需从另一个线程填充队列(这是线程安全的)。

基本上,您可以在协议中存储任意数量的状态;它会一直存在,直到你完成。您可以将命令发送到服务器作为对它的消息的响应,或者您设置一些计划来执行您的操作。或两者兼而有之。

答案 1 :(得分:3)

您可能想要使用Service

服务是Twisted应用程序中的一些功能,它们是启动和停止的,并且是代码的其他部分进行交互的很好的抽象。例如,在这种情况下,你可能有一个SayStuffToServerService(我知道,可怕的名字,但不知道更多关于它的工作,这是我能在这里做的最好:))暴露了这样的事情:

class SayStuffToServerService:
    def __init__(self, host, port):
        # this is the host and port to connect to

    def sendToServer(self, whatToSend):
        # send some line to the remote server

    def startService(self):
        # call me before using the service. starts outgoing connection efforts.

    def stopService(self):
        # clean reactor shutdowns should call this method. stops outgoing
        # connection efforts.

(这可能是你需要的所有界面,但是你可以相当清楚地向它添加内容。)

这里的startService()stopService()方法正是Twisted的服务所揭示的。有用的是,有一个预制的Twisted服务,它就像一个TCP客户端,负责处理所有反应堆的东西。它是twisted.application.internet.TCPClient,它接受​​远程主机和端口的参数,以及用于处理实际连接尝试的ProtocolFactory。

这是SayStuffToServerService,作为TCPClient的子类实现:

from twisted.application import internet

class SayStuffToServerService(internet.TCPClient):
    factoryclass = SayStuffToServerProtocolFactory

    def __init__(self, host, port):
        self.factory = self.factoryclass()
        internet.TCPClient.__init__(self, host, port, self.factory)

    def sendToServer(self, whatToSend):
        # we'll do stuff here

(参见下面的SayStuffToServerProtocolFactory。)

使用此服务架构在很多方面都很方便;您可以将服务组合在一个容器中,这样当您希望活动的应用程序的不同部分时,它们都会停止并作为一个容器启动。将应用程序的其他部分实现为单独的服务可能是很有意义的。您可以将服务作为子服务设置为application - twistd寻找的神奇名称,以便了解如何初始化,守护和关闭您的应用。实际上是的,让我们现在添加一些代码来做到这一点。

from twisted.application import service

...

application = service.Application('say-stuff')

sttss = SayStuffToServerService('localhost', 65432)
sttss.setServiceParent(service.IServiceCollection(application))

这就是全部。现在,当您在twistd下运行此模块时(即,用于调试,twistd -noy saystuff.py),application将在正确的反应器下启动,它将依次启动SayStuffToServerService,开始连接localhost:65432,它将使用服务的factory属性来建立连接和协议。你不需要打电话给reactor.run()或者自己把东西连接到反应堆上。

所以我们还没有实现SayStuffToServerProtocolFactory。因为听起来你希望你的客户端重新连接,如果它已经失去了连接(因此sendToServer的来电者通常只是假设有一个工作连接),我会把这个协议工厂位于ReconnectingClientFactory之上。

from twisted.internet import protocol

class SayStuffToServerProtocolFactory(protocol.ReconnectingClientFactory):
    _my_live_proto = None
    protocol = SayStuffToServerProtocol

这是一个非常好的最小定义,它将继续尝试与我们指定的主机和端口建立传出的TCP连接,并且每次都实例化一个SayStuffToServerProtocol。当我们无法连接时,这个类将做出良好的,良好的指数退避,这样你的网络就不会受到重创(你可以设置一个最长的等待时间)。协议的责任是分配给_my_live_proto并调用此工厂的resetDelay()方法,以便指数退避将继续按预期工作。现在是协议:

class SayStuffToServerProtocol(basic.LineReceiver):
    def connectionMade(self):
        # if there are things you need to do on connecting to ensure the
        # connection is "all right" (maybe authenticate?) then do that
        # before calling:
        self.factory.resetDelay()
        self.factory._my_live_proto = self

    def connectionLost(self, reason):
        self.factory._my_live_proto = None
        del self.factory

    def sayStuff(self, stuff):
        self.sendLine(stuff)

    def lineReceived(self, line):
        # do whatever you want to do with incoming lines. often it makes sense
        # to have a queue of Deferreds on a protocol instance like this, and
        # each incoming response gets sent to the next queued Deferred (which
        # may have been pushed on the queue after sending some outgoing
        # message in sayStuff(), or whatever).
        pass

这是在twisted.protocols.basic.LineReceiver之上实现的,但如果您的协议不是面向行的,那么它可以与任何其他类型的协议一起使用。

唯一剩下的就是将服务连接到正确的协议实例。这就是工厂保留_my_live_proto属性的原因,该属性应在成功建立连接时设置,并在连接丢失时清除(设置为无)。这是SayStuffToServerService.sendToServer的新实现:

class NotConnectedError(Exception):
    pass

class SayStuffToServerService(internet.TCPClient):

    ...

    def sendToServer(self, whatToSend):
        if self.factory._my_live_proto is None:
            # define here whatever behavior is appropriate when there is no
            # current connection (in case the client can't connect or
            # reconnect)
            raise NotConnectedError
        self.factory._my_live_proto.sayStuff(whatToSend)

现在将它们集中在一个地方:

from twisted.application import internet, service
from twisted.internet import protocol
from twisted.protocols import basic

class SayStuffToServerProtocol(basic.LineReceiver):
    def connectionMade(self):
        # if there are things you need to do on connecting to ensure the
        # connection is "all right" (maybe authenticate?) then do that
        # before calling:
        self.factory.resetDelay()
        self.factory._my_live_proto = self

    def connectionLost(self, reason):
        self.factory._my_live_proto = None
        del self.factory

    def sayStuff(self, stuff):
        self.sendLine(stuff)

    def lineReceived(self, line):
        # do whatever you want to do with incoming lines. often it makes sense
        # to have a queue of Deferreds on a protocol instance like this, and
        # each incoming response gets sent to the next queued Deferred (which
        # may have been pushed on the queue after sending some outgoing
        # message in sayStuff(), or whatever).
        pass

class SayStuffToServerProtocolFactory(protocol.ReconnectingClientFactory):
    _my_live_proto = None
    protocol = SayStuffToServerProtocol

class NotConnectedError(Exception):
    pass

class SayStuffToServerService(internet.TCPClient):
    factoryclass = SayStuffToServerProtocolFactory

    def __init__(self, host, port):
        self.factory = self.factoryclass()
        internet.TCPClient.__init__(self, host, port, self.factory)

    def sendToServer(self, whatToSend):
        if self.factory._my_live_proto is None:
            # define here whatever behavior is appropriate when there is no
            # current connection (in case the client can't connect or
            # reconnect)
            raise NotConnectedError
        self.factory._my_live_proto.sayStuff(whatToSend)

application = service.Application('say-stuff')

sttss = SayStuffToServerService('localhost', 65432)
sttss.setServiceParent(service.IServiceCollection(application))

希望能够提供足够的框架来开始。有时需要做很多工作来处理客户端断开连接,只是按照你想要的方式,或处理来自服务器的无序响应,或处理各种超时,取消挂起的请求,允许多个池连接等,等,但这应该有所帮助。

答案 2 :(得分:0)

扭曲的框架是基于事件的编程;并且本质上,它的方法都是在异步中调用的,结果是通过延迟对象获得的。

框架的本质对于协议开发来说是恰当的,只需要改变你对传统顺序编程的看法。 Protocol类就像一个有限状态机,其事件包括:连接make,连接丢失,接收数据。 您可以将客户端代码转换为FSM,然后很容易适应Protocol类。

以下是我想表达的一个粗略的例子。有点胭脂,但这是我现在可以提供的:

class SyncTransport(Protocol):
    # protocol
    def dataReceived(self, data):
        print 'receive data', data
    def connectionMade(self):
        print 'i made a sync connection, wow'
        self.transport.write('x')
        self.state = I_AM_LIVING
    def connectionLost(self):
        print 'i lost my sync connection, sight'
    def send(self, data):
        if self.state == I_AM_LIVING:
            if data == 'x':
              self.transport.write('y')
           if data == 'Y':
              self.transport.write('z')
              self.state = WAITING_DEAD
        if self.state == WAITING_DEAD:
              self.transport.close()