3

I'm using twisted python to make a simple SSH server on a custom port. I create a Port object with port = reactor.listenTCP(_port, sshfactory), where _port is a variable that holds the integer of the port. I free up the port when shutting down the server with the commands port.loseConnection() and port.connectionLost(reason=None). If I attempt to start the server, stop it, and start it again, I get the titular error 'Port' object has no attribute 'socket'

Edit: full error message:

Unhandled error in Deferred:
Traceback (most recent call last):
  File "C:\Python27\lib\site-packages\twisted\internet\base.py", line 1175, in mainLoop
    self.runUntilCurrent()
  File "C:\Python27\lib\site-packages\twisted\internet\base.py", line 779, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "C:\Python27\lib\site-packages\twisted\internet\defer.py", line 238, in callback
    self._startRunCallbacks(result)
  File "C:\Python27\lib\site-packages\twisted\internet\defer.py", line 307, in _startRunCallbacks
    self._runCallbacks()
--- <exception caught here> ---
  File "C:\Python27\lib\site-packages\twisted\internet\defer.py", line 323, in _runCallbacks
    self.result = callback(self.result, *args, **kw)
  File "C:\Python27\lib\site-packages\twisted\internet\task.py", line 736, in <lambda>
    d.addCallback(lambda ignored: callable(*args, **kw))
  File "C:\Python27\lib\site-packages\twisted\internet\tcp.py", line 981, in connectionLost
    self._closeSocket()
  File "C:\Python27\lib\site-packages\twisted\internet\tcp.py", line 92, in _closeSocket
    skt = self.socket
exceptions.AttributeError: 'Port' object has no attribute 'socket'

Edit2: stopListening code sample (Python 2.7):

stopListening sample.py

from twisted.cred import portal, checkers, credentials
from twisted.conch import error, avatar, recvline, interfaces as conchinterfaces
from twisted.conch.ssh import factory, userauth, connection, keys, session, common
from twisted.conch.insults import insults
from twisted.application import service, internet
from twisted.internet import reactor, protocol
from zope.interface import implements
import threading
import os

class SSHDemoProtocol(recvline.HistoricRecvLine):
    def __init__(self, user):
        self.user = user

    def connectionMade(self):
        recvline.HistoricRecvLine.connectionMade(self)
        self.showPrompt()

    def showPrompt(self):
        self.terminal.write("$ ")

class SSHDemoRealm:
    implements(portal.IRealm)
    def requestAvatar(self, avatarId, mind, *interfaces):
        if conchinterfaces.IConchUser in interfaces:
            return interfaces[0], SSHDemoAvatar(avatarId), lambda: None
        else:
            raise Exception("No supported interfaces found.")

def getRSAKeys():
    if not (os.path.exists('public.key') and os.path.exists('private.key')):
        # generate a RSA keypair
        print("Generating RSA keypair...")
        from Crypto.PublicKey import RSA
        KEY_LENGTH = 1024
        rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)
        publicKeyString = keys.makePublicKeyString(rsaKey)
        privateKeyString = keys.makePrivateKeyString(rsaKey)
        # save keys for next time
        file('public.key', 'w+b').write(publicKeyString)
        file('private.key', 'w+b').write(privateKeyString)
        print("done.")
    else:
        publicKeyString = file('public.key').read()
        privateKeyString = file('private.key').read()
    return publicKeyString, privateKeyString

def launchServer():
    _port = 4564
    password = 'password'
    sshFactory = factory.SSHFactory()
    sshFactory.portal = portal.Portal(SSHDemoRealm())
    users = {'user': password}
    sshFactory.portal.registerChecker(
        checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
    pubKeyString, privKeyString = getRSAKeys()
    sshFactory.publicKeys = {
        'ssh-rsa': keys.getPublicKeyString(data=pubKeyString)}
    sshFactory.privateKeys = {
        'ssh-rsa': keys.getPrivateKeyObject(data=privKeyString)}
    global port
    port = reactor.listenTCP(_port, sshFactory)
    reactor.addSystemEventTrigger('before', 'shutdown', stopServer)
    reactor.run(installSignalHandlers=False)

def startServer():
    thread = threading.Thread(target=launchServer)
    thread.start()

def stopServer():
    global port
    port.stopListening()
    reactor.stop()
    reactor.crash()

startServer()
stopServer()
startServer()

traceback:

>>> Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/paul/Desktop/Down2Home/stopListening sample.py", line 62, in launchServer
    port = reactor.listenTCP(_port, sshFactory)
  File "/usr/local/lib/python2.7/dist-packages/Twisted-9.0.0-py2.7-linux-x86_64.egg/twisted/internet/posixbase.py", line 355, in listenTCP
    p.startListening()
  File "/usr/local/lib/python2.7/dist-packages/Twisted-9.0.0-py2.7-linux-x86_64.egg/twisted/internet/tcp.py", line 855, in startListening
    raise CannotListenError, (self.interface, self.port, le)
CannotListenError: Couldn't listen on any:4564: [Errno 98] Address already in use.
4

2 回答 2

4

listenTCP returns an IListeningPort; IListeningPort does not have loseConnection or connectionLost methods. Instead, it has stopListening. The presence of those methods that you're calling is an unfortunate accident. You should try using the publicly advertised interfaces and see if that works.

(Also, you should post a completely runnable bit of code, so that we actually know what you mean by "stop it and start it again", as well as a complete traceback, rather than just a snippet of an error message.)

Additionally, Twisted APIs may not be invoked from arbitrary threads. This code will provoke undefined and difficult to predict behavior from Twisted:

def stopServer():
    global port
    port.stopListening()
    reactor.stop()
    reactor.crash()

for several reasons. First, startServer set up the application and started the reactor in another thread. This means port.stopListening() is not allowed because it's a Twisted API being called in the wrong thread. Second, reactor.crash() is really only a testing helper, and even in that area, its use is strongly discouraged as better testing techniques have been developed since reactor.crash() was invented.

You might get away with something like this to fix these problems:

from twisted.internet.threads import blockingCallFromThread

def startServer():
    global thread
    thread = threading.Thread(target=launchServer)
    thread.start()

def stopServer():
    global port, thread
    blockingCallFromThread(reactor, port.stopListening)
    reactor.callFromThread(reactor.stop)
    thread.join()
    thread = None

Of course, the use of globals isn't ideal, but I'm sticking with them here to keep the code close to your original.

What this does is:

  • Call port.stopListening in the reactor thread using blockingCallFromThread. Additionally, this will block until the Deferred returned by stopListening fires. This means that when that line is done, the port will no longer be in use.
  • Call reactor.stop in the reactor thread using reactor.callFromThread. Since this call is intended to stop the reactor, I don't think it's safe to use blockingCallFromThread since once the reactor stops the inter-thread communication mechanism may no longer work. Also, reactor.stop doesn't return a Deferred so there's nothing useful to block on anyway.
  • Wait for the reactor to stop running by joining the thread in which it is running (which will block until launchServer returns, which it will do as soon as reactor.run() returns, which it will once the reactor stops).

However, you may also want to consider not using threads at all in this way. There's no particular reason to do so, at least none I can determine from this minimal example. If you have some other use of threads that seems necessary to you, that might make good fodder for another SO question. :)

于 2013-05-05T00:32:42.537 回答
0

The solution turned out to be ditching the threads entirely, and using a built-in method of interfacing with a GUI. tksupport.install(root) Twisted is not thread-safe.

于 2013-06-13T05:03:36.683 回答