Net :: Telnet - 以UTF-8放置或打印字符串

时间:2014-09-02 16:08:23

标签: ruby encoding utf-8 telnet

我正在使用一个API,我必须通过telnet连接将客户端信息作为Json对象发送(非常奇怪,我知道^^)。 我是德国人,所以客户信息经常包含变音符号或ß。

我的程序:

  1. 我生成一个包含所有命令信息的哈希。
  2. 我将Hash转换为Json对象。
  3. 我将Json对象转换为字符串(.to_s)。
  4. 我使用Net :: Telnet.puts命令发送字符串。
  5. 我的puts命令如下所示:(cmd是Json对象)

    host.puts(cmd.to_s.force_encoding('UTF-8'))
    

    在我看到的日志文件中,Json对象不包含变音符号,例如:ü而不是ü

    我在UTF-8中证明了字符串(有或没有force_encoding()命令)。所以我认为puts命令不会以UTF-8发送字符串。

    是否可以以UTF-8发送命令?我怎么能这样做?

    整个方法:

    host = Net::Telnet::new(
        'Host' => host_string,
        'Port' => port_integer,
        'Output_log' => 'log/'+Time.now.strftime('%Y-%m-%d')+'.log',
        'Timeout' => false,
        'Telnetmode' => false,
        'Prompt' => /\z/n
    )
    
    def send_cmd_container(host, cmd, params=nil)
        cmd = JSON.generate({'*C'=>'se','Q'=>[get_cmd(cmd, params)]})
        host.puts(cmd.to_s.force_encoding('UTF-8'))
        add_request_to_logfile(cmd)
    end
    
    def get_cmd(cmd, params=nil)
        if params == nil
            return {'*C'=>'sq','CMD'=>cmd}
        else
            return {'*C'=>'sq','CMD'=>cmd,'PARAMS'=>params}
        end
    end
    

    增加:

    我也通过这种方法记录我的发送请求:

    def add_request_to_logfile(request_string)
        directory = 'log/'
        File.open(File.join(directory, Time.now.strftime('%Y-%m-%d')+'.log'), 'a+') do |f|
            f.puts ''
            f.puts '> '+request_string
        end
    end
    

    在日志文件中,我的请求也不包含UTF-8变音符号,例如:ü

1 个答案:

答案 0 :(得分:3)

TL; DR

设置'Binmode' => true并使用Encoding::BINARY

以上 应该适合您。如果您对原因感兴趣,请继续阅读。


Telnet实际上没有“编码”的概念。 Telnet只有两种模式:普通模式假设你发送7位ASCII字符,二进制模式假设你发送8位字节。你无法告诉Telnet“这是UTF-8”因为Telnet不知道这意味着什么。你可以告诉它“这是ASCII-7”或“这是一个8位字节的序列”,就是这样。

这可能看起来像坏消息,但它实际上是个好消息,因为UTF-8恰好将文本编码为8位字节的序列。例如,früh是五个字节:66 72 c3 bc 68。这在Ruby中很容易确认:

puts str = "\x66\x72\xC3\xBC\x68"
# => früh
puts str.bytes.size
# => 5

在Net :: Telnet中,我们可以通过将'Binmode' => true选项传递给Net::Telnet::new来启用二进制模式。但还有一件事我们要做:告诉Ruby将字符串视为二进制数据,即一个8位字节的序列。

您已尝试使用String#force_encoding,但您可能没有意识到String#force_encoding实际上并未将字符串从一种编码转换为另一种编码。它的目的不是改变数据的编码 - 它的目的是告诉Ruby 已经的数据编码:

str = "früh"   # => "früh"
p str.encoding # => #<Encoding:UTF-8>
p str[2]       # => "ü"

p str.bytes    # => [ 102, 114, 195, 188, 104 ] # This is the decimal represent-
                                                # ation of the hexadecimal bytes
                                                # we saw before, `66 72 c3 bc 68`

str.force_encoding(Encoding::BINARY) # => "fr\xC3\xBCh"
p str[2]       # => "\xC3"

p str.bytes    # => [ 102, 114, 195, 188, 104 ] # Same bytes!

现在我会告诉你一个小秘密:Encoding::BINARY只是Encoding::ASCII_8BIT的别名。由于ASCII-8BIT没有多字节字符,因此Ruby将ü显示为两个独立的字节\xC3\xBC。这些字节在ASCII-8BIT中不是可打印字符,因此Ruby显示\x##转义码,但数据没有改变 - 只有Ruby打印它的方式已经改变。

所以这就是事情:即使Ruby现在调用字符串BINARY或ASCII-8BIT而不是UTF-8,它仍然是相同的字节,这意味着它仍然是UTF-8 。然而,更改编码它被“标记”为,意味着当Net :: Telnet(相当于)data[n]时,它将始终获得一个字节(而不是像UTF-8那样获得多字节字符),这正是我们想要的。

所以......

host = Net::Telnet::new(
         # ...all of your other options...
         'Binmode' => true
       )

def send_cmd_container(host, cmd, params=nil)
  cmd = JSON.generate('*C' => 'se','Q' => [ get_cmd(cmd, params) ])
  cmd.force_encoding(Encoding::BINARY)
  host.puts(cmd)
  # ...
end

(注意:JSON.generate始终返回UTF-8字符串,因此您无需执行例如cmd.to_s。)

有用的诊断

检查Net :: Telnet实际发送(和接收)的数据的快速方法是设置'Dump_log'选项(与设置'Output_log'选项的方式相同)。它会将发送和接收的数据写入hexdump格式的日志文件,这样您就可以查看发送的字节是否正确。例如,我启动了一个测试服务器(nc -l 5555)并发送了字符串frühhost.puts "früh".force_encoding(Encoding::BINARY)),这就是记录的内容:

> 0x00000: 66 72 c3 bc  68 0a                                  fr..h.

你可以看到它发送了六个字节:前两个是fr,接下来的两个是ü,最后两个是h和换行符。在右侧,不可打印字符的字节显示为.,ergo fr..h.。 (出于同样的原因,我发送了字符串I❤NY并在右列中看到了I...NY.,因为是UTF-8中的三个字节:e2 9d a4)。

因此,如果您设置'Dump_log'并发送ü,则应在输出中看到c3 bc。如果你这样做,恭喜你 - 你发送了UTF-8!

P.S。阅读Yehuda Katz的文章Ruby 1.9 Encodings: A Primer and the Solution for Rails。事实上,每年阅读一次。这真的非常有用。