Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>You may want to use a <a href="http://twistedmatrix.com/documents/current/api/twisted.application.service.html" rel="nofollow">Service</a>.</p> <p>Services are pieces of functionality within a Twisted app which are started and stopped, and are nice abstractions for other parts of your code to interact with. For example, in this case you might have a SayStuffToServerService (I know, terrible name, but without knowing more about its job it was the best I could do here :) ) that exposed something like this:</p> <pre><code>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. </code></pre> <p>(That might be all the interface you need, but it should be fairly clear where you can add things to this.)</p> <p>The <code>startService()</code> and <code>stopService()</code> methods here are just what Twisted's Services expose. And helpfully, there is a premade Twisted Service which acts like a TCP client and takes care of all the reactor stuff for you. It's <code>twisted.application.internet.TCPClient</code>, which takes arguments for a remote host and port, along with a ProtocolFactory to take care of handling the actual connection attempt.</p> <p>Here is the SayStuffToServerService, implemented as a subclass of <code>TCPClient</code>:</p> <pre><code>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 </code></pre> <p>(See below for the SayStuffToServerProtocolFactory.)</p> <p>Using this Service architecture is convenient in a lot of ways; you can group Services together in one container, so that they all get stopped and started as one when you have different parts of your app that you want active. It may make good sense to implement other parts of your app as separate Services. You can set Services as child services to <code>application</code>- the magic name that <code>twistd</code> looks for in order to know how to initialize, daemonize, and shut down your app. Actually yes, let's add some code to do that now.</p> <pre><code>from twisted.application import service ... application = service.Application('say-stuff') sttss = SayStuffToServerService('localhost', 65432) sttss.setServiceParent(service.IServiceCollection(application)) </code></pre> <p>That's all. Now when you run this module under <code>twistd</code> (i.e., for debugging, <code>twistd -noy saystuff.py</code>), that <code>application</code> will be started under the right reactor, and it will in turn start the SayStuffToServerService, which will start a connection effort to localhost:65432, which will use the service's <code>factory</code> attribute to set up the connection and the Protocol. You don't need to call <code>reactor.run()</code> or attach things to the reactor yourself anymore.</p> <p>So we haven't implemented SayStuffToServerProtocolFactory yet. Since it sounds like you would prefer that your client reconnect if it has lost the connection (so that callers of <code>sendToServer</code> can usually just assume that there's a working connection), I'm going to put this protocol factory on top of <code>ReconnectingClientFactory</code>.</p> <pre><code>from twisted.internet import protocol class SayStuffToServerProtocolFactory(protocol.ReconnectingClientFactory): _my_live_proto = None protocol = SayStuffToServerProtocol </code></pre> <p>This is a pretty nice minimal definition, which will keep trying to make outgoing TCP connections to the host and port we specified, and instantiate a SayStuffToServerProtocol each time. When we fail to connect, this class will do nice, well-behaved exponential backoff so that your network doesn't get hammered (you can set a maximum wait time). It will be the responsibility of the Protocol to assign to <code>_my_live_proto</code> and call this factory's <code>resetDelay()</code> method, so that exponential backoff will continue to work as expected. And here is that Protocol now:</p> <pre><code>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 </code></pre> <p>This is implemented on top of <code>twisted.protocols.basic.LineReceiver</code>, but would work as well with any other sort of Protocol, in case your protocol isn't line-oriented.</p> <p>The only thing left is hooking up the Service to the right Protocol instance. This is why the Factory keeps a <code>_my_live_proto</code> attribute, which should be set when a connection is successfully made, and cleared (set to None) when that connection is lost. Here's the new implementation of <code>SayStuffToServerService.sendToServer</code>:</p> <pre><code>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) </code></pre> <p>And now to tie it all together in one place:</p> <pre><code>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)) </code></pre> <p>Hopefully that gives enough of a framework with which to start. There is sometimes a lot of plumbing to do to handle client disconnections just the way you want, or to handle out-of-order responses from the server, or handle various sorts of timeout, canceling pending requests, allowing multiple pooled connections, etc, etc, but this should help.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload