【问题标题】:Net::SFTP connection not closingNet::SFTP 连接未关闭
【发布时间】:2026-02-08 15:40:01
【问题描述】:

我有一个 Ruby on Rails(Rails 3.2.14 和 ruby​​ 1.9.3)应用程序,可以将 2 个文件上传到远程 SFTP 服务器。 SFTP 代码为:

require 'net/sftp'
Rails.logger.info("Creating SFTP connection")
uri = URI.parse('sftp://'+ host)
Net::SFTP.start(uri.host,'user', :password=>'password',:port=>port) do |sftp|
    Rails.logger.info("SFTP Connection created, uploading files.")
    sftp.upload!("public/file1.txt", "./file1.txt")
    Rails.logger.info("First file uploaded.")
    sftp.upload!("file2.txt", "./file2.txt")
    Rails.logger.info("Both files uploaded, terminating connection.")
end
Rails.logger.info("Connection terminated.")

两个文件都已正确上传到远程服务器,但连接似乎没有关闭。当我执行此功能并分析我的控制台时,我不断收到错误消息,我看到“两个文件都已上传,正在终止连接”。记录器消息正在运行,但之后没有任何内容。我试过使用

sftp.close(:handle)
sftp.close!(:handle)
#and
sftp.close_connection()

但它们都没有工作。关于为什么会发生这种情况以及如何纠正它的任何想法?我通过单实例引擎场云服务器运行它。

编辑 这些是我日志中的最后几行: 创建 SFTP 连接 SFTP 连接已创建,正在上传文件。 第一个文件上传。 两个文件都已上传,正在终止连接。

在那之后,什么都没有。当使用 'tail -f' 命令查看我的日志时,日志会上升到最后一行,并且应用程序会重定向到内部服务器错误页面。

【问题讨论】:

  • 问题不在于连接没有关闭。当块结束时它会自动关闭。您不断收到的错误是什么?
  • 我收到一个内部服务器错误,将我定向到公用文件夹中的 500.html 文件。有什么方法可以让我准确验证这个错误是什么或者是什么导致了这个错误?
  • 最好看日志log/production.log
  • Rails.logger.info("两个文件都上传,正在终止连接。")后日志终止。这是日志显示的最后一条消息。之后,错误发生,我被重定向到服务器错误页面并且日志没有显示任何内容。考虑到日志没有移动到 Rails.logger.info("Connection denied."),我认为这是一个连接终止问题......
  • 您已发布到您正在执行此操作的控制器和操作。

标签: ruby-on-rails net-sftp


【解决方案1】:

简答

Net::SFTP.start('host', 'user', password: 'pass', port: 22) do |sftp|

  # Do stuff

end

相当于:

session = Net::SSH.start('host', 'user', password: 'pass', port: 22)
sftp = Net::SFTP::Session.new(session)
sftp.connect!

# Do stuff

sftp.close_channel unless sftp.nil?
session.close unless session.nil?

更好的答案

对于在不使用自动关闭块的情况下无法找到实际关闭连接的方法的人,我可以通过以下方式实现此目的:

require 'net/ssh'
require 'net/sftp'

begin

  # Instance SSH/SFTP session :
  session = Net::SSH.start('host', 'user', password: 'pass', port: 22)
  sftp = Net::SFTP::Session.new(session)

  # Always good to timeout :
  Timeout.timeout(10) do
    sftp.connect! # Establish connection

    # Do stuff

  end

rescue Timeout::Error => e

  # Do some custom logging
  puts e.message 

ensure

  # Close SSH/SFTP session
  sftp.close_channel unless sftp.nil? # Close SFTP
  session.close unless session.nil? # Then SSH

  # If you really really really wanna make sure it's closed,
  # and raise after 10 seconds delay
  Timeout.timeout(10) do
    sleep 1 until (sftp.nil? or sftp.closed?) and (session.nil? or session.closed?)
  end

end

如果您在执行其他任务之前没有关闭连接,您有时可能会在 Rails 中遇到类似这样的错误:

IOError (not opened for reading) # Not closed when rendering controller action
ActionView::Template::Error (not opened for reading) # Not closed when rendering template

【讨论】:

  • 非常感谢这里对 Net:SFTP:start do 结构等价的分解。
  • 虽然这是一个非常好的细分。人们需要注意Timeout 及其潜在影响。 jvns.ca/blog/2015/11/27/…
  • @TheRealMrCrowley :谢谢,我不知道。幸运的是,我在生产中的应用程序中总是使用明确的rescue Timeout::Error :)
  • 请注意,您不需要手动创建Net::SSH 会话。如果您使用Net::SFTP.start,您仍然可以使用sftp.session.close 关闭SSH 会话(首先执行sftp.close_channel)。
【解决方案2】:

我不知道为什么,但是重写代码以包含“net/ssh”并通过 SSH 显式创建 sftp 连接有效!

require 'net/ssh'
require 'net/sftp'
#upload a file or directory to the remote host
Rails.logger.info("Creating SFTP connection")
session=Net::SSH.start('host','user', :password=>'password',:port=>port)
sftp=Net::SFTP::Session.new(session).connect!
Rails.logger.info("SFTP Connection created, uploading files.")
sftp.upload!("file1.txt", "./file1.txt")
Rails.logger.info("First file uploaded.")
sftp.upload!("file2.txt", "./file2.txt")
Rails.logger.info("Both files uploaded, terminating connection.")
Rails.logger.info("Connection terminated.")

也许有人可以解释一下为什么这个有效而另一个无效。

【讨论】:

  • 我遇到了同样的问题,而这个解决方法是唯一解决它的方法。
【解决方案3】:

如果您在块的末尾发送 EOF,自动关闭块将能够关闭:

Net::SFTP.start(host, user, port: port, password: password) do |sftp|
    # ... do your things
    sftp.channel.eof!
end

【讨论】: