如何正确触发python twisted传输?
我被要求编写一个连接到服务器的类,异步地向服务器发送各种命令,然后将返回的数据提供给客户端。我被要求用Python来做这件事,Python对我来说是一种新语言。我开始四处挖掘,发现了Twisted框架,它提供了一些非常好的抽象(协议、协议工厂、反应堆),可以做很多事情,如果我要推出自己的基于套接字的应用程序,我就必须做这些事情。考虑到我必须解决的问题,这似乎是一个正确的选择 我已经浏览了很多web上的示例(大部分),但是我仍然没有看到一个创建一个客户端的好例子,该客户端将通过网络发送多个命令,并且我维护我创建的连接。在本例中,服务器(我无法控制)在发送响应后不会断开连接。那么,设计客户机的正确方法是什么,这样我就可以用各种方式逗服务器开心了 现在我这样做:如何正确触发python twisted传输?,python,twisted,Python,Twisted,我被要求编写一个连接到服务器的类,异步地向服务器发送各种命令,然后将返回的数据提供给客户端。我被要求用Python来做这件事,Python对我来说是一种新语言。我开始四处挖掘,发现了Twisted框架,它提供了一些非常好的抽象(协议、协议工厂、反应堆),可以做很多事情,如果我要推出自己的基于套接字的应用程序,我就必须做这些事情。考虑到我必须解决的问题,这似乎是一个正确的选择 我已经浏览了很多web上的示例(大部分),但是我仍然没有看到一个创建一个客户端的好例子,该客户端将通过网络发送多个命令,并
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()),而不仅仅是在建立连接时 视情况而定。以下是一些可能性: 我想 方法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:您的编辑现在提到从另一个线程触发。请随意查看twisted文档,以了解proto.sendString
是否是线程安全的。你可以直接打电话,但我不知道。不过,方法3是线程安全的。只需从另一个线程填充队列(这是线程安全的)
基本上,您可以在协议中存储任意数量的状态;它将一直存在,直到你完成。您可以向服务器发送命令作为对其发送给您的消息的响应,或者设置一些日程安排来完成您的工作。或者两者兼有。您可能需要使用一个
服务是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。)
使用这种服务架构在很多方面都很方便;您可以将服务组合在一个容器中,这样当您的应用程序中有不同的部分需要激活时,它们都可以作为一个整体停止和启动。将应用程序的其他部分作为单独的服务来实现可能很有意义。您可以将服务设置为应用程序的子服务
——这是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
),该应用程序将在正确的反应器下启动,它将依次启动saystuff-toserver服务,该服务将启动到localhost:65432的连接工作,它将使用服务的工厂属性来设置连接和协议。您不再需要调用reactor.run()
或自己将东西附加到reactor
所以我们还没有实现SayStuffToServerProtocolFactory。由于听起来您更希望在客户端失去连接时重新连接(这样,sendToServer
的调用者通常可以假定存在工作连接),因此我将把这个协议工厂放在重新连接客户端工厂
之上
from twisted.internet import protocol
class SayStuffToServerProtocolFactory(protocol.ReconnectingClientFactory):
_my_live_proto = None
protocol = SayStuffToServerProtocol
这是一个非常好的最小定义,它将继续尝试与我们指定的主机和端口建立传出TCP连接,并每次实例化一个SaySuffToServerProtocol。当我们连接失败时,这个类将进行良好的、性能良好的指数退避,这样您的网络就不会受到影响(您可以设置最大等待时间)。协议将负责分配给\u my\u live\u 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
之上实现的,但在协议不面向行的情况下,它也适用于任何其他类型的协议
剩下的就是将服务连接到正确的协议实例。这就是工厂保留\u my\u live\u proto
属性的原因,该属性应在成功建立连接时设置,并在连接丢失时清除(设置为无)。下面是saytufftoserverservice.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))
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()