我们正在使用paramiko创建一个大量使用其get_pty
或invoke_shell
功能的连接库。我们的库使用这些通道与目标设备进行交互。
但是每当我们使用multiprocessing
库时,我们都无法在子进程中使用paramiko连接句柄。 transport
在子进程中关闭。
Is there a way to tell paramiko not to close the connection/channel at fork.
这是用于复制问题的示例程序
from paramiko import SSHClient, AutoAddPolicy
from multiprocessing import Process
import logging
log = logging.getLogger("paramiko.transport").setLevel(1)
client = SSHClient()
client.set_missing_host_key_policy(AutoAddPolicy())
client.connect(hostname="localhost")
def simple_work(handle):
print("==== ENTERED CHILD PROCESS =====")
stdin, stdout, stderr = handle.exec_command("ifconfig")
print(stdout.read())
print("==== EXITING CHILD PROCESS =====")
p = Process(target=simple_work, args=(client,))
p.start()
p.join(2)
print("==== MAIN PROCESS AFTER JOIN =====")
stdin, stdout, stderr = client.exec_command("ls")
print(stdout.read())
这是错误
==== ENTERED CHILD PROCESS =====
Success for unrequested channel! [??]
==== MAIN PROCESS AFTER JOIN =====
Traceback (most recent call last):
File "repro.py", line 22, in <module>
stdin, stdout, stderr = client.exec_command("ls")
File "/Users/vivejha/Projects/cisco/lib/python3.4/site-packages/paramiko/client.py", line 401, in exec_command
chan = self._transport.open_session(timeout=timeout)
File "/Users/vivejha/Projects/cisco/lib/python3.4/site-packages/paramiko/transport.py", line 702, in open_session
timeout=timeout)
File "/Users/vivejha/Projects/cisco/lib/python3.4/site-packages/paramiko/transport.py", line 823, in open_channel
raise e
paramiko.ssh_exception.SSHException: Unable to open channel.
很少有重要事项需要注意
如果我尝试访问子进程中的client
。首先,它根本不起作用。
其次,主流程中的句柄也令人惊讶地消失了。我不知道如何促进这种从孩子到父母的沟通以及原因。
最大的问题是程序最终挂起,异常很好,但挂起是最不期望的。
如果我不在子进程中使用client
,并做一些其他工作,那么父进程中的client
不会受到影响并且照常工作。
注意:transport.py中有一些名为atfork
的内容声称可以控制此行为。但令人惊讶的是,即使对该方法中的代码进行评论也没有任何影响。在paramiko的整个代码库中也没有引用atfork
。
PS:我正在使用最新的paramiko,这个程序是在Mac上运行的
答案 0 :(得分:1)
当套接字涉及fork
时,这只是一个基本问题。两个进程共享同一个套接字,但只有一个可以使用它。试想一下,两个不同的进程正在管理一个套接字。它们都处于不同的状态,例如一个人可能向远程端发送和接收数据,而另一个人则处于完全不同的加密状态。想想nonces /初始化向量,当两个进程发生分歧时,它们只会在分叉后无效。
问题的解决方案显然是从MultiProcessing
切换到MultiThreading
。这样,您只有一个在所有线程之间共享的ssh连接。如果你真的想使用fork,那么你必须在fork中为每个fork创建一个新连接。
请参阅transport.py
def atfork(self):
"""
Terminate this Transport without closing the session. On posix
systems, if a Transport is open during process forking, both parent
and child will share the underlying socket, but only one process can
use the connection (without corrupting the session). Use this method
to clean up a Transport object without disrupting the other process.
在paramiko日志中,您将看到父进程从远程端接收到SSH_DISCONNECT_MSG,错误为:Packet corrupt
。很可能是由于父母处于不同的加密状态并发送了服务器无法理解的数据包。
DEBUG:lala:==== ENTERED CHILD PROCESS =====
DEBUG:lala:<paramiko.SSHClient object at 0xb74bf1ac>
DEBUG:lala:<paramiko.Transport at 0xb6fed82cL (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>
DEBUG:paramiko.transport:[chan 1] Max packet in: 34816 bytes
WARNING:paramiko.transport:Success for unrequested channel! [??]
DEBUG:lala:==== MAIN PROCESS AFTER JOIN =====
WARNING:lala:<socket._socketobject object at 0xb706ef7c>
DEBUG:paramiko.transport:[chan 1] Max packet in: 34816 bytes
INFO:paramiko.transport:Disconnect (code 2): Packet corrupt
以下是使用concurrent.futures的基本MultiThreading示例:
from concurrent.futures import ThreadPoolExecutor
def simple_work(handle):
print("==== ENTERED CHILD PROCESS =====")
stdin, stdout, stderr = handle.exec_command("whoami")
print(stdout.read())
print("==== EXITING CHILD PROCESS =====")
with ThreadPoolExecutor(max_workers=2) as executor:
future = executor.submit(simple_work, client)
print(future.result())
print("==== MAIN PROCESS AFTER JOIN =====")
stdin, stdout, stderr = client.exec_command("echo AFTER && whoami")
print(stdout.read())
另请注意,在大多数情况下,您甚至不需要引入额外的线程。 Paramiko exec_command
alread会生成一个新线程,并且在您尝试从任何伪文件stdout
,stderr
读取之前不会阻止。这意味着,你也可以执行一些命令并从stdout读取。但请记住,由于缓冲区已满,paramiko可能会停止运行。