我正在使用一个API,我必须通过telnet连接将客户端信息作为Json对象发送(非常奇怪,我知道^^)。 我是德国人,所以客户信息经常包含变音符号或ß。
我的程序:
.to_s
)。我的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变音符号,例如:ü
答案 0 :(得分:3)
设置'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üh
(host.puts "früh".force_encoding(Encoding::BINARY)
),这就是记录的内容:
> 0x00000: 66 72 c3 bc 68 0a fr..h.
你可以看到它发送了六个字节:前两个是f
和r
,接下来的两个是ü
,最后两个是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。事实上,每年阅读一次。这真的非常有用。