我被要求编写一个连接到服务器的类,异步发送服务器各种命令,然后将返回的数据提供给客户端。我被要求用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()),而不仅仅是在建立连接时。
答案 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()