Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to send data with twisted protocol via factory

I'm writing a client implementing a custom protocol, and have a factory for it. My problem is the following: my client has bi-dir communication, and sometimes I want to tell it "send this data". But all I have is the factory object:

class MyFactory(ClientFactory):
    protocol = MyProtocol

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

    def send_message(self, msg):
        self.protocol.send_message(msg)

So I create a factory and have a factory object, I don't the protocol object. When send_message above is called I get an error because self.protocol is just a class, not an object.

How can I do this? Should I also expose the protocol for connection in addition to the factory?

Thanks

like image 449
zaharpopov Avatar asked May 22 '11 13:05

zaharpopov


1 Answers

You have access to all of the objects you want. The factory is responsible for creating protocol instances, so if you want to keep the protocol instance around where the factory can use it, override buildProtocol and save the instance:

class MyFactory(ClientFactory):
    protocol = MyProtocol

    ...

    def buildProtocol(self, address):
        proto = ClientFactory.buildProtocol(self, address)
        self.connectedProtocol = proto
        return proto

However, this approach is lacking in one important feature. It does not make it easy to tell when buildProtocol has been called and connectedProtocol has been set. If you try to use this attribute naively:

factory = MyFactory()
reactor.connectTCP(host, port, factory)
factory.connectedProtocol.send_message(...)

The code will fail with an AttributeError because the connection has not yet actually been set up. Since Twisted is event driven, you need to make sure to use this code by responding to an event that says the connection has been set up.

You might do this by firing a callback when the protocol is constructed instead of just setting an attribute. Twisted actually has a helper factory which does something like this already:

from twisted.internet.protocol import ClientCreator

cc = ClientCreator(reactor, MyProtocol)
whenConnected = cc.connectTCP(host, port)

# Or the equivalent with endpoints
#  from twisted.internet.endpoints import TCP4ClientEndpoint
#  from twisted.internet.protocol import ClientFactory
#  endpoint = TCP4ClientEndpoint(reactor, host, port)
#  factory = ClientFactory()
#  factory.protocol = MyProtocol
#  whenConnected = endpoint.connect(factory)

def cbConnected(connectedProtocol):
    connectedProtocol.send_message(...)

def ebConnectError(reason):
    # Connection attempt failed, perhaps retry
    ...

whenConnected.addCallbacks(cbConnected, ebConnectError)

You could also save the reference to connectedProtocol in the cbConnected callback so that you can continue to use it later on. You might also start whatever other operations want to use the connected protocol in cbConnected, so that they don't try to use the connection before it is actually available.

like image 117
Jean-Paul Calderone Avatar answered Sep 30 '22 17:09

Jean-Paul Calderone