当前位置: 首页 > 面试题库 >

如果要检查recv_ready(),是否必须检查exit_status_ready?

柯英奕
2023-03-14
问题内容

我正在使用以下命令运行远程命令:

ssh = paramiko.SSHClient()
ssh.connect(host)
stdin, stdout, stderr = ssh.exec_command(cmd)

现在,我想获取输出。我看过这样的事情:

# Wait for the command to finish
while not stdout.channel.exit_status_ready():
    if stdout.channel.recv_ready():
        stdoutLines = stdout.readlines()

但这有时似乎永远不会运行readlines()(即使应该在stdout上有数据)。对我而言,这似乎意味着stdout.channel.exit_status_ready()为True时,stdout.channel.recv_ready()不一定已经准备好(正确)。

这样合适吗?

# Wait until the data is available
while not stdout.channel.recv_ready():
    pass

stdoutLines = stdout.readlines()

也就是说,在等待recv_ready()说数据准备就绪之前,我真的必须首先检查退出状态吗?

我如何知道在无限循环中等待stdout.channel.recv_ready()变为True之前是否应该在stdout上存储数据(如果不应该有任何stdout输出,就不会这样)?


问题答案:

也就是说,在等待recv_ready()说数据准备就绪之前,我真的必须首先检查退出状态吗?

stdout/stderr不能。即使还没有完成,也可以从远程进程接收数据(例如)。另外,某些sshd实现甚至不提供远程proc的退出状态,在这种情况下,您会遇到问题,请参见paramiko
doc:exit_status_ready

等待exit_status_code短暂的远程命令的问题在于,本地线程接收到exit_code的速度可能比检查循环条件的速度快。在这种情况下,您将永远不会进入循环,并且永远不会调用readlines()。这是一个例子:

# spawns new thread to communicate with remote
# executes whoami which exits pretty fast
stdin, stdout, stderr = ssh.exec_command("whoami") 
time.sleep(5)  # main thread waits 5 seconds
# command already finished, exit code already received
#  and set by the exec_command thread.
# therefore the loop condition is not met 
#  as exit_status_ready() already returns True 
#  (remember, remote command already exited and was handled by a different thread)
while not stdout.channel.exit_status_ready():
    if stdout.channel.recv_ready():
        stdoutLines = stdout.readlines()

我怎么知道stdout在无限循环中等待stdout.channel.recv_ready()变为True之前是否应该有数据(如果不应该有任何stdout输出,则不会)?

channel.recv_ready() 仅表示缓冲区中有未读数据。

def recv_ready(self):
    """
    Returns true if data is buffered and ready to be read from this
    channel.  A ``False`` result does not mean that the channel has

closed;
it means you may need to wait before more data arrives.


这意味着可能是由于联网(延迟的数据包,重传等)或只是您的远程进程未stdout/stderr定期写入而导致的recv_ready为False。因此,具有recv_ready()as循环条件可能会导致您的代码过早返回,因为它有时会产生True(当远程进程写入stdout并且本地通道线程接收到该输出时),而有时会产生False(例如,远程proc在休眠中并且未在迭代中写入stdout)。

除此之外,人们偶尔会遇到paramiko挂起,这可能与stdout/stderr
缓冲区已满有关(可能与Popen和挂起proc的问题有关,当您从不读取stdout/stderr并且内部缓冲区已满时)。

下面的代码实现了一个分块的解决方案,以stdout/stderr在通道打开时从清空缓冲区读取数据。

def myexec(ssh, cmd, timeout, want_exitcode=False):
  # one channel per command
  stdin, stdout, stderr = ssh.exec_command(cmd) 
  # get the shared channel for stdout/stderr/stdin
  channel = stdout.channel

  # we do not need stdin.
  stdin.close()                 
  # indicate that we're not going to write to that channel anymore
  channel.shutdown_write()

  # read stdout/stderr in order to prevent read block hangs
  stdout_chunks = []
  stdout_chunks.append(stdout.channel.recv(len(stdout.channel.in_buffer)))
  # chunked read to prevent stalls
  while not channel.closed or channel.recv_ready() or channel.recv_stderr_ready(): 
      # stop if channel was closed prematurely, and there is no data in the buffers.
      got_chunk = False
      readq, _, _ = select.select([stdout.channel], [], [], timeout)
      for c in readq:
          if c.recv_ready(): 
              stdout_chunks.append(stdout.channel.recv(len(c.in_buffer)))
              got_chunk = True
          if c.recv_stderr_ready(): 
              # make sure to read stderr to prevent stall    
              stderr.channel.recv_stderr(len(c.in_stderr_buffer))  
              got_chunk = True  
      '''
      1) make sure that there are at least 2 cycles with no data in the input buffers in order to not exit too early (i.e. cat on a >200k file).
      2) if no data arrived in the last loop, check if we already received the exit code
      3) check if input buffers are empty
      4) exit the loop
      '''
      if not got_chunk \
          and stdout.channel.exit_status_ready() \
          and not stderr.channel.recv_stderr_ready() \
          and not stdout.channel.recv_ready(): 
          # indicate that we're not going to read from this channel anymore
          stdout.channel.shutdown_read()  
          # close the channel
          stdout.channel.close()
          break    # exit as remote side is finished and our bufferes are empty

  # close all the pseudofiles
  stdout.close()
  stderr.close()

  if want_exitcode:
      # exit code is always ready at this point
      return (''.join(stdout_chunks), stdout.channel.recv_exit_status())
  return ''.join(stdout_chunks)

channel.closed万一通道过早关闭,这只是最终退出条件。读取块之后,代码立即检查是否已接收exit_status,并且在此期间没有新数据被缓冲。如果新数据到达或没有收到exit_status,则代码将继续尝试读取块。一旦远程进程退出并且缓冲区中没有新数据,我们就假定已经读取了所有内容并开始关闭通道。请注意,万一您想要退出状态,请务必等到收到退出状态,否则paramiko可能会永远阻塞。

这样可以确保缓冲区不会填满并使您的进程挂起。exec_command仅在退出远程命令且本地缓冲区中没有数据时返回。通过使用代码select()而不是在繁忙的循环中进行轮询,该代码对cpu的友好程度也更高,但对于短命命令而言可能会更慢一些。

仅供参考,为了防止某些无限循环,可以设置一个通道超时,该超时时间在一段时间内没有数据到达时触发

 chan.settimeout(timeout)
 chan.exec_command(command)


 类似资料:
  • 我想检查一个特定的应用程序是否需要在运行时处理Android Marshmallow运行时权限。 以下假设正确吗? 下面是我详细阐述的表格,例如,向具有Android Marshmallow运行时权限的用户请求权限时,需要使用权限。 我知道支持库助手的存在,但我想在这种特殊情况下避免使用它们。

  • 问题内容: AngularJs中的内联方法是否可以检查某物是否为数组? 我本以为这样可以工作: 我已经证实它实际上是一个数组。有什么我想念的东西吗? 问题答案: 您可以戴上瞄准镜… 小提琴

  • 问题内容: 是否需要进行显式的if(log.isDebugEnabled()){…}检查? 我的意思是,我已经看到一些帖子提到log.debug(“something”)进行隐式调用,以查看调试模式日志记录是否已启用,然后再进行日志记录。我是否缺少某些东西,或者在使用此步骤之前有中间步骤要执行? 谢谢! 与 编辑:在上面写过:http : //java.sg/whether-to-do-a-isd

  • 我很难让这东西工作。基本上,一旦我单击用户配置的删除按钮,这个函数就会被调用。用户可以有多个配置。发生的情况是,一旦我删除了一个配置,它会寻找另一个配置,如果找到了,它会将其设置为当前配置文件。第一位工作正常,但如果返回什么,而不是输入它返回我一个错误... 代码: 错误: 请记住,我对Laravel和整个MVC环境非常陌生。

  • 问题内容: 谁能指出如何检查选择查询是否返回非空结果集? 例如,我有下一个查询: 我应该像下一个那样做吗: 测试结果集是否不为空? 问题答案: 使用@@ ROWCOUNT: 根据SQL Server联机丛书: 返回受最后一条语句影响的行数。如果行数超过20亿,请使用ROWCOUNT_BIG。

  • 问题内容: 好吧,我是SQL的新手,我刚刚读到,存储过程始终返回一个值,确定该过程中的查询是否已成功执行是一种很好的做法。 所以我有一个带有select语句的简单存储过程,如果要执行,我想返回1,否则返回-1。 您能告诉我如何用SQL编写该条件吗? 如果有关系,我的数据库是MS SQL Server。 谢谢你。 问题答案: 使用输出参数返回成功状态以及Try..Catch块