将以下内容放入 hello.py 文件中(如果还没有,请加easy_install paramiko
):
hostname,username,password='fill','these','in'
import paramiko
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
适当填写第一行。
现在输入
python hello.py
你会看到一些ls输出。
现在改为输入
python
然后从解释器类型
import hello
瞧,瞧!它挂了!如果您将代码包装在函数foo
中并执行import hello; hello.foo()
,它将会解除支持。
为什么Paramiko在模块初始化时使用时会挂起? Paramiko如何知道它首先在模块初始化期间被使用?
答案 0 :(得分:18)
Paramiko为底层传输使用单独的线程。 你应该永远不会拥有一个产生线程的模块作为导入的副作用。据我所知,有一个可用的导入锁,所以当你的模块中的子线程尝试另一次导入时,它可以无限期地阻塞,因为你的主线程仍然持有锁。 (可能还有其他我不了解的问题)
通常,模块在导入时不应具有任何类型的副作用,否则您将获得不可预测的结果。用__name__ == '__main__'
技巧暂缓执行,你会没事的。
[编辑] 我似乎无法创建一个简单的测试用例来重现这种死锁。我仍然认为这是导入的线程问题,因为auth代码正在等待永远不会触发的事件。这可能是paramiko或python中的一个错误,但好消息是,如果你做得对,你就不应该看到它;)
这是一个很好的例子,为什么你总是希望尽量减少副作用,以及为什么函数式编程技术变得越来越普遍。
答案 1 :(得分:3)
正如JimB指出,当python尝试在ssh连接尝试期间首次使用时隐式导入str.decode('utf-8')
解码器时,它是导入问题。有关详细信息,请参阅分析部分。
一般来说,人们不能强调你应该避免让模块在导入时自动生成新线程。如果可以的话,尽量避免使用魔术模块代码,因为它几乎总会导致不必要的副作用。
对于您的问题,简单明了的解决方法 - 如前所述 - 是将您的代码放在if __name__ == '__main__':
主体中,只有在执行此特定模块时才执行,并且在执行时不执行这个mmodule是由其他模块导入的。
(不推荐)另一种解决方法是在调用SSHClient.connect()
之前在代码中执行虚拟str.decode(' utf-8') - 请参阅下面的分析。 / p>
那么这个问题的根本原因是什么?
分析(简单密码验证)
提示:如果你想在python导入中调试线程并设置threading._VERBOSE = True
paramiko.SSHClient().connect(.., look_for_keys=False, ..)
隐式为您的连接生成一个新线程。如果打开paramiko.transport
的调试输出,也可以看到此信息。 [Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L
这基本上是SSHClient.connect()
的一部分。调用client.py:324::start_client()
时,会创建一个锁transport.py:399::event=threading.Event()
并启动线程transport.py:400::self.start()
。请注意,start()
方法将执行类transport.py:1565::run()
方法。
transport.py:1580::self._log(..)
打印我们的日志消息"启动线程"然后进入transport.py:1584::self._check_banner()
。
check_banner
做了一件事。它检索ssh标志(来自服务器的第一个响应)transport.py:1707::self.packetizer.readline(timeout)
(注意超时只是套接字读取超时),最后检查换行符
否则超时
如果收到服务器标题,它会尝试utf-8解码响应字符串packet.py:287::return u(buf)
以及发生死锁的地方。 u(s, encoding='utf-8')
执行str.decode(' utf-i')并通过encodings.utf8
隐式导入encodings:99
中的encodings.search_function
,导致导入死锁。
所以一个肮脏的修复就是只导入一次utf-8解码器,以便不会因为模块导入副作用而阻止该指定导入。 (''.decode('utf-8')
)
<强>修正强>
脏修复 - 不推荐
import paramiko
hostname,username,password='fill','these','in'
''.decode('utf-8') # dirty fix
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
良好的修复
import paramiko
if __name__ == '__main__':
hostname,username,password='fill','these','in'
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
答案 2 :(得分:0)
“”。decode(“utf-8”)对我不起作用,我最终做到了这一点。
$dialogScope.user = {};
我有一个paramiko包装器实现了。 https://github.com/bucknerns/sshaolin