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.
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:
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.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.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. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With