关闭 Twisted conch SSH 连接的正确方法是什么?有没有明确的方法来做到这一点?
我见过的所有 Twisted conch 示例都会关闭 SSH 通道然后停止反应器。反应堆关闭似乎可以处理关闭连接。但是,我将 wxreactor 与 wxPython 一起使用,我不想停止反应器,但我想在完成后关闭 ssh 连接。
在查看 tcsconnection 之后,似乎 serviceStopped() 方法是要走的路。它关闭所有打开的通道并在完成后运行 _cleanupGlobalDeferreds() ,但随后我开始收到如下异常:
Unhandled Error
Traceback (most recent call last):
File "C:\Users\me\venv\lib\site-packages\twisted\internet\tcp.py", line 203, in doRead
return self._dataReceived(data)
File "C:\Users\me\venv\lib\site-packages\twisted\internet\tcp.py", line 209, in _dataReceived
rval = self.protocol.dataReceived(data)
File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\transport.py", line 438, in dataReceived
self.dispatchMessage(messageNum, packet[1:])
File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\transport.py", line 460, in dispatchMessage
messageNum, payload)
--- <exception caught here> ---
File "C:\Users\me\venv\lib\site-packages\twisted\python\log.py", line 84, in callWithLogger
return callWithContext({"system": lp}, func, *args, **kw)
File "C:\Users\me\venv\lib\site-packages\twisted\python\log.py", line 69, in callWithContext
return context.call({ILogContext: newCtx}, func, *args, **kw)
File "C:\Users\me\venv\lib\site-packages\twisted\python\context.py", line 118, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "C:\Users\me\venv\lib\site-packages\twisted\python\context.py", line 81, in callWithContext
return func(*args,**kw)
File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\service.py", line 44, in packetReceived
return f(packet)
File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\connection.py", line 228, in ssh_CHANNEL_DATA
channel = self.channels[localChannel]
exceptions.KeyError: 0
通道关闭后,我似乎仍在从服务器获取数据。#twisted 中的某个人似乎认为我不应该自己调用 serviceStopped(),因为它应该由 Twisted 的不同部分自动调用。
我在 Twisted 源代码中进行了一些探索,发现 serviceStopped 应该由 tcstSSHClientTransport.connectionLost() 调用。
我正在跟踪我的 SFTP 客户端对象并通过它们的传输属性访问 SSH 连接。这是一个可以在本地运行以演示该问题的示例。可以在这里获取原始数据。
from os.path import basename
import sys
from twisted.conch.client.connect import connect
from twisted.conch.client.options import ConchOptions
from twisted.internet.defer import Deferred
from twisted.conch.ssh import channel, userauth
from twisted.conch.ssh.common import NS
from twisted.conch.ssh.connection import SSHConnection
from twisted.conch.ssh.filetransfer import FXF_WRITE, FXF_CREAT, \
FXF_TRUNC, FileTransferClient
from twisted.internet import reactor, defer
from twisted.python.log import startLogging
ACTIVE_CLIENTS = {}
USERNAME = 'user' # change me!
PASSWORD = 'password' # change me!
HOST = ('hostname', 22) # change me!
TEST_FILE_PATH = __file__
TEST_FILE_NAME = basename(__file__)
def openSFTP(user, host):
conn = SFTPConnection()
options = ConchOptions()
options['host'], options['port'] = host
conn._sftp = Deferred()
auth = SimpleUserAuth(user, conn)
connect(options['host'], options['port'], options, verifyHostKey, auth)
return conn._sftp
def verifyHostKey(ui, hostname, ip, key):
return defer.succeed(True)
class SimpleUserAuth(userauth.SSHUserAuthClient):
def getPassword(self):
return defer.succeed(PASSWORD)
class SFTPConnection(SSHConnection):
def serviceStarted(self):
self.openChannel(SFTPChannel())
class SFTPChannel(channel.SSHChannel):
name = 'session'
def channelOpen(self, ignoredData):
d = self.conn.sendRequest(self, 'subsystem', NS('sftp'),
wantReply=True)
d.addCallback(self._cbFTP)
d.addErrback(self.printErr)
def _cbFTP(self, ignore):
client = FileTransferClient()
client.makeConnection(self)
self.dataReceived = client.dataReceived
ACTIVE_CLIENTS.update({self.conn.transport.transport.addr: client})
self.conn._sftp.callback(None)
def printErr(self, msg):
print msg
return msg
@defer.inlineCallbacks
def main():
d = openSFTP(USERNAME, HOST)
_ = yield d
client = ACTIVE_CLIENTS[HOST]
d = client.openFile(TEST_FILE_NAME, FXF_WRITE | FXF_CREAT | FXF_TRUNC, {})
df = yield d
sf = open(TEST_FILE_PATH, 'rb')
d = df.writeChunk(0, sf.read())
_ = yield d
sf.close()
d = df.close()
_ = yield d
ACTIVE_CLIENTS[HOST].transport.loseConnection()
# loseConnection() call above causes the following log messages:
# [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] sending close 0
# [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] unhandled request for exit-status
# [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] remote close
# [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] closed
# I can see the channel closed on the server side:
# sshd[4485]: debug1: session_exit_message: session 0 channel 0 pid 4486
# sshd[4485]: debug1: session_exit_message: release channel 0
# sshd[4485]: debug1: session_by_channel: session 0 channel 0
ACTIVE_CLIENTS[HOST].transport.conn.transport.loseConnection()
# loseConnection() call above does not close the SSH connection.
reactor.callLater(5, reactor.stop)
# Stopping the reactor closes the SSH connection and logs the following messages:
# [SSHClientTransport,client] connection lost
# [SSHClientTransport,client] Stopping factory <twisted.conch.client.direct.SSHClientFactory instance at 0x02E5AF30>
# [-] Main loop terminated.
# On the server side:
# sshd[4485]: Closing connection to xxx.xxx.xxx.xxx
if __name__ == '__main__':
startLogging(sys.stdout)
reactor.callWhenRunning(main)
reactor.run()
要关闭 SSH 连接,我正在调用ACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection()
which calls t.c.c.d.SSHClientTransport.sendDisconnect()
。这是 sendDisconnect() 方法:
def sendDisconnect(self, code, reason):
if self.factory.d is None:
return
d, self.factory.d = self.factory.d, None
transport.SSHClientTransport.sendDisconnect(self, code, reason)
d.errback(error.ConchError(reason, code))
调用此方法时,self.factory.d 似乎始终为 None,因此它在不调用 tcstSSHClientTransport.sendDisconnect() 的情况下返回。我认为它最初是 tccdconnect 中的一个延迟集,但在某些时候它被设置为 None。
我怀疑 SSHClientTransport.loseConnection() 是关闭 SSH 连接的正确方法,但是为什么 self.factory.d 设置为 None 当 twisted 期望它是别的东西时?
如果 lostConnection() 不是关闭 SSH 连接的正确方法,有人能指出我正确的方向吗?