pythontwistedtwistd

twistd Application (.tac) with TCPServer not working, while regular Twisted .py works with listenTCP


I currently trying to convert an application based on twisted to the twistd Twisted Application framework (TAC).

The application works if I start the .py, but not with the "twistd -noy zmq2tcp.tac" daemon. The ZMQ connections seems properly opened, but if started with the tac file it does not Listen on port 2323.

Please explain me the reason why in this case the listenTCP works but not the internet.TCPServer

The zmq2tcp.tac file:

#!/usr/bin/python
# **- encoding: utf-8 -**
from twisted.application import internet, service
from twisted.application.service import Application

from txzmq import ZmqFactory, ZmqEndpoint, ZmqSubConnection, ZmqPushConnection

from zmq2tcp import *

LISTEN_PORT = 2323

class ListenService(service.Service):
    def __init__(self):
        self.zf = ZmqFactory()
        self.minidoFactory = MinidoServerFactory()

    def startService(self):
        self.sube = ZmqEndpoint('connect', 'tcp://localhost:5557')
        self.push = ZmqEndpoint('connect', 'tcp://localhost:5558')
        self.subconn = ZmqSubConnection(self.zf, self.sube)
        self.subconn.subscribe('')

        # Create the resource
        self.minidoFactory.pushconn = ZmqPushConnection(self.zf, self.push)
        self.subconn.gotMessage = self.minidoFactory.send2tcp
        return internet.TCPServer(LISTEN_PORT, self.minidoFactory)

    def stopService(self):
        del self.sube
        del self.push
        del self.subconn

application = Application('ZMQ2TCP')
service = ListenService()
service.setServiceParent(application)

The zmq2tcp.py file:

#!/usr/bin/env python
# **- encoding: utf-8 -**
"""
    Minido-Unleashed is a set of programs to control a home automation
    system based on minido from AnB S.A.

    Please check http://kenai.com/projects/minido-unleashed/

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    ***

    This program connects to a STOMP server, and allow dual way communication
    with the minido bus, checking the validity of the packet before sending.
"""

###############################################################################
from __future__ import print_function
from twisted.application import internet, service
from twisted.internet.protocol import Protocol, ReconnectingClientFactory
from twisted.internet.protocol import Factory


# Other imports
import datetime
import time
import sys
import msgpack

# minido
from protocol import *

# txZMQ
from txzmq import ZmqFactory, ZmqEndpoint, ZmqPubConnection, ZmqSubConnection, ZmqPushConnection

MINIDO_LISTEN_PORT = 2323 

class MinidoServerFactory(Factory):
    def __init__(self):   
        self.connections = []

    def startedConnecting(self, connector):
        print('Started to connect.')

    def buildProtocol(self, addr):
        return MinidoProtocol(self)

    def clientConnectionLost(self, connector, reason):
        print('Lost connection.  Reason:', reason)
        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)

    def clientConnectionFailed(self, connector, reason):
        print('Connection failed. Reason:', reason)
        ReconnectingClientFactory.clientConnectionFailed(self, connector,
                                                         reason)
    def recv_message(self, message):
        print(': TCP to STOMP : %s' % (' '.join(map(lambda i: '{0:02X}'.format(i),message))))
        pushconn.push(msgpack.packb(message))

    def send2tcp(self, rawmessage, tag):
        message = msgpack.unpackb(rawmessage)
        if type(message) is tuple:
            print(": STOMP to %i TCP client(s) : %s" % (
                len(self.connections),
                ' '.join(map(lambda i: '{0:02X}'.format(i),message))))
            for conn in self.connections:
                conn.send_data(message)

if __name__ == '__main__':
    from twisted.internet import reactor
    zf = ZmqFactory()
    minidoFactory = MinidoServerFactory()
    sube = ZmqEndpoint('connect', 'tcp://localhost:5557')
    subconn = ZmqSubConnection(zf, sube)
    subconn.subscribe('') 

    subconn.gotMessage = minidoFactory.send2tcp

    push = ZmqEndpoint('connect', 'tcp://localhost:5558')
    minidoFactory.pushconn = ZmqPushConnection(zf, push)
    reactor.listenTCP(MINIDO_LISTEN_PORT, minidoFactory)
    reactor.run()

Solution

  • The problem is that in your ListenService.startService you are creating and returning a TCPServer service, but you are not starting that service.

    IService.startService does not return a value, so rather than return internet.TCPServer(LISTEN_PORT, self.minidoFactory), do internet.TCPServer(LISTEN_PORT, self.minidoFactory).startService().

    This has an obvious problem though, which is that your stopService will then not remember where that TCPServer is to stop it later. It would be better to factor this to create the TCPServer as soon as possible, and start/stop it along with your service, like this:

    class ListenService(service.Service):
        def __init__(self):
            self.zf = ZmqFactory()
            self.minidoFactory = MinidoServerFactory()
            self.tcpService = internet.TCPServer(LISTEN_PORT, self.minidoFactory)
    
        def startService(self):
            self.sube = ZmqEndpoint('connect', 'tcp://localhost:5557')
            self.push = ZmqEndpoint('connect', 'tcp://localhost:5558')
            self.subconn = ZmqSubConnection(self.zf, self.sube)
            self.subconn.subscribe('')
    
            # Create the resource
            self.minidoFactory.pushconn = ZmqPushConnection(self.zf, self.push)
            self.subconn.gotMessage = self.minidoFactory.send2tcp
            self.tcpService.startService()
    
        def stopService(self):
            del self.sube
            del self.push
            del self.subconn
            return self.tcpService.stopService()