Ruby,Telnet,无超时读取多行响应

Ruby, Telnet, read multiline response without timeout

我需要一些 hints/help,如何将多行响应读入变量。 我当前的命令导致多行响应,但在那之后我超时了。

这是我的连接设置方式:

connection = Net::Telnet.new('Host' => host,'Port' => 4800, 'Telnetmode' => false, 'Timeout' => 1)

这是我的请求以及我如何保存它:

puts "Weather request\n"
connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)
puts "#{parsed}\n\n"

这是错误:

/usr/lib/ruby/1.9.1/net/telnet.rb:558:in `waitfor': timed out while waiting for more data (Timeout::Error)
    from /usr/lib/ruby/1.9.1/net/telnet.rb:695:in `cmd'
    from ruby_check.rb:37:in `<main>'

我的回复是多 JSON 行,像这样:

{"City":"Tallinn", "Degrees":"23"}
{"City":"Berlin", "Degrees":"23"}
{"City":"Helsinki", "Degrees":"23"}
{"City":"Stockholm", "Degrees":"23"}

您将超时设置为一秒,但未指定 str 是什么。您可以尝试增加超时值,甚至将其设置为 false。相信这是 .cmd 的结果,试试这个:

connection = Net::Telnet.new(
  "Host" => host, "Port" => 4800, 
  "Telnetmode" => false, "Timeout" => false)

puts "Weather request...\n"

str = connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)

puts "#{parsed}\n\n"

为什么超时?

Net::Telnet documentation 说:

For some protocols, it will be possible to specify the Prompt option once when you create the Telnet object and use cmd() calls; for others, you will have to specify the response sequence to look for as the Match option to every cmd() call, or call puts() and waitfor() directly; for yet others, you will have to use sysread() instead of waitfor() and parse server responses yourself.

这在与 Net::Telnet#cmd method's documentation 结合时更有意义,它表示方法:

sends a string to the host, and reads in all received data until is sees the prompt or other matched sequence.

您没有指定自定义 PromptMatch 选项,因此 #cmd 正在等待来自服务器的与默认 Net::Telnet 提示匹配的内容(/[$%#>] \z/n) 表示消息结束。 如果消息没有以那种提示结束,那么它将永远等待。

可能的解决方案

匹配服务器的提示

如果服务器确实发送了某种提示来指示它已完成发送数据并且您应该键入下一个命令,您可以将匹配它的正则表达式传递给 Net::Telnet 初始化程序。例如,如果服务器提示您使用 command:,您可以使用:

connection = Net::Telnet.new(
  "Prompt" => /command: \z/,
  # …
)

匹配响应的结尾

如果没有提示,但您等待的响应以特定字符序列结束,您可以在调用 #cmd 时显式指定 Match 选项。例如,如果您的响应是单个 JSON 数组,它将以 ] 结尾,因此您可以使用:

connection.cmd("String" => "{weather}", "Match" => "]") { |c| print c }

放弃 Net::Telnet 并使用 TCPSocket

如果没有提示也没有已知的结局,你可以尝试使用Net::Telnet对象的底层TCPSocket来读取数据而不使用#cmd:

connection.puts("{weather}")
connection.sock.readline

在这一点上,使用 Net::Telnet 而不是普通的 TCPSocket 可能没有太大好处。