消息大小变化 TCPServer Ruby
Message size varies TCPServer Ruby
我正在使用 AVL (Skypatrol TT8750+),它发送的消息(使用 TCP)应该是 59 字节长,但它总是发送第一条消息(该消息包含一些关于 AVL 的信息,所以用户可以识别它)的 33 个字节。
所以问题是,我如何处理 ruby 上那些不同大小的消息?
require 'socket'
portnumber = 12050
socketServer = TCPServer.open(portnumber)
while true
Thread.new(socketServer.accept) do |connection|
puts "Accepting connection from: #{connection.peeraddr[2]}"
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
out_file = File.new(file_name, "w+")
begin
while connection
incomingData = connection.gets()
if incomingData != nil
incomingData = incomingData
end
hex_line = incomingData.unpack('H*')[0]
out_file.puts(hex_line)
puts "Incoming: #{hex_line}"
end
rescue Exception => e
# Displays Error Message
puts "#{ e } (#{ e.class })"
ensure
connection.close
puts "ensure: Closing"
end
end
end
这是我正在使用的实验代码。
解决方法很简单
require 'socket'
require 'celluloid/io'
portnumber = 12050
socketServer = TCPServer.open(portnumber)
while true
Thread.new(socketServer.accept) do |connection|
puts "Accepting connection from: #{connection.peeraddr[2]}"
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
out_file = File.new(file_name, "w+")
messagecounter = 1
begin
while connection
if messagecounter == 1
incomingData = conection.recv(33)
messagecounter += 1
else
incomingData = connection.recv(59)
end
if incomingData != nil
incomingData = incomingData.unpack('H*')[0]
end
out_file.puts(incomingData)
puts "Incoming: #{incomingData}"
end
rescue Exception => e
# Displays Error Message
puts "#{ e } (#{ e.class })"
ensure
connection.close
puts "ensure: Closing"
end
end
end
我只需要一个额外的变量和一个 if 来自动递增变量,仅此而已。
我发布此答案是为了解释我对 Anderson 的答案所做的评论。大多数代码不是我的。
将 if
移出循环
当if
语句在循环中时,每次循环运行时都会对其进行评估,从而增加CPU指令的数量和每个循环的复杂性。
您可以通过将条件语句移出循环来提高性能,如下所示:
require 'socket'
require 'celluloid/io'
portnumber = 12050
socketServer = TCPServer.open(portnumber)
incomingData = nil
while true
Thread.new(socketServer.accept) do |connection|
puts "Accepting connection from: #{connection.peeraddr[2]}"
# this should probably be changed,
# it ignores the possibility of two connections arriving at the same timestamp.
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
out_file = File.new(file_name, "w+")
begin
if connection
incomingData = conection.recv(33)
if incomingData != nil
incomingData = incomingData.unpack('H*')[0]
out_file.puts(incomingData)
puts "Incoming: #{incomingData}"
end
end
while connection
incomingData = connection.recv(59)
if incomingData != nil
incomingData = incomingData.unpack('H*')[0]
out_file.puts(incomingData)
puts "Incoming: #{incomingData}"
end
end
rescue Exception => e
# Displays Error Message
puts "#{ e } (#{ e.class })"
ensure
connection.close
out_file.close
puts "ensure: Closing"
end
end
end
优化recv
方法
另一个我应该提到的优化(但不会在这里实现)是 recv
方法调用。
这既是优化,也是应该解决的错误的可能来源。
recv
是一个系统调用,由于网络消息可能在 TCP/IP 数据包中组合(或分段),因此调用 recv
可能比处理内部消息更昂贵解决碎片和溢出状态的数据缓冲区。
重新考虑每个客户端线程设计
我还建议避免每个客户端线程设计。
一般来说,对于少数客户来说,这可能并不重要。
但是,随着客户端的增加和线程变得更加繁忙,您可能会发现系统在上下文切换上花费的资源多于实际任务。
另一个问题可能是每个线程需要分配的堆栈(Ruby 线程需要 1Mb 或 2Mb,如果我没记错的话)...在最好的情况下,1,000 个客户端将需要超过 1 GB 的仅为堆栈分配内存(我忽略了内核结构数据 table 和其他资源)。
我会考虑使用 EventMachine 或 Iodine(我是 iodine 的作者,所以我有偏见)。
事件设计可以为您节省很多资源。
例如(未经测试):
require 'iodine'
# define the protocol for our service
class ExampleProtocol
@timeout = 10
def on_open
puts "New Connection Accepted."
# this should probably be changed,
# it ignores the possibility of two connections arriving at the same timestamp.
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
@out_file = File.new(file_name, "w+")
# a rolling buffer for fragmented messages
@expecting = 33
@msg = ""
end
def on_message buffer
length = buffer.length
pos = 0
while length >= @expecting
@msg << (buffer[pos, @expecting])
out_file.puts(msg.unpack('H*')[0])
length -= @expecting
pos += @expecting
@expecting = 59
@msg.clear
end
if(length > 0)
@msg << (buffer[pos, length])
@expecting = 59-length
end
end
def on_close
@out_file.close
end
end
# create the service instance
Iodine.listen 12050, ExampleProtocol
# start the service
Iodine.start
我正在使用 AVL (Skypatrol TT8750+),它发送的消息(使用 TCP)应该是 59 字节长,但它总是发送第一条消息(该消息包含一些关于 AVL 的信息,所以用户可以识别它)的 33 个字节。
所以问题是,我如何处理 ruby 上那些不同大小的消息?
require 'socket'
portnumber = 12050
socketServer = TCPServer.open(portnumber)
while true
Thread.new(socketServer.accept) do |connection|
puts "Accepting connection from: #{connection.peeraddr[2]}"
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
out_file = File.new(file_name, "w+")
begin
while connection
incomingData = connection.gets()
if incomingData != nil
incomingData = incomingData
end
hex_line = incomingData.unpack('H*')[0]
out_file.puts(hex_line)
puts "Incoming: #{hex_line}"
end
rescue Exception => e
# Displays Error Message
puts "#{ e } (#{ e.class })"
ensure
connection.close
puts "ensure: Closing"
end
end
end
这是我正在使用的实验代码。
解决方法很简单
require 'socket'
require 'celluloid/io'
portnumber = 12050
socketServer = TCPServer.open(portnumber)
while true
Thread.new(socketServer.accept) do |connection|
puts "Accepting connection from: #{connection.peeraddr[2]}"
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
out_file = File.new(file_name, "w+")
messagecounter = 1
begin
while connection
if messagecounter == 1
incomingData = conection.recv(33)
messagecounter += 1
else
incomingData = connection.recv(59)
end
if incomingData != nil
incomingData = incomingData.unpack('H*')[0]
end
out_file.puts(incomingData)
puts "Incoming: #{incomingData}"
end
rescue Exception => e
# Displays Error Message
puts "#{ e } (#{ e.class })"
ensure
connection.close
puts "ensure: Closing"
end
end
end
我只需要一个额外的变量和一个 if 来自动递增变量,仅此而已。
我发布此答案是为了解释我对 Anderson 的答案所做的评论。大多数代码不是我的。
将 if
移出循环
当if
语句在循环中时,每次循环运行时都会对其进行评估,从而增加CPU指令的数量和每个循环的复杂性。
您可以通过将条件语句移出循环来提高性能,如下所示:
require 'socket'
require 'celluloid/io'
portnumber = 12050
socketServer = TCPServer.open(portnumber)
incomingData = nil
while true
Thread.new(socketServer.accept) do |connection|
puts "Accepting connection from: #{connection.peeraddr[2]}"
# this should probably be changed,
# it ignores the possibility of two connections arriving at the same timestamp.
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
out_file = File.new(file_name, "w+")
begin
if connection
incomingData = conection.recv(33)
if incomingData != nil
incomingData = incomingData.unpack('H*')[0]
out_file.puts(incomingData)
puts "Incoming: #{incomingData}"
end
end
while connection
incomingData = connection.recv(59)
if incomingData != nil
incomingData = incomingData.unpack('H*')[0]
out_file.puts(incomingData)
puts "Incoming: #{incomingData}"
end
end
rescue Exception => e
# Displays Error Message
puts "#{ e } (#{ e.class })"
ensure
connection.close
out_file.close
puts "ensure: Closing"
end
end
end
优化recv
方法
另一个我应该提到的优化(但不会在这里实现)是 recv
方法调用。
这既是优化,也是应该解决的错误的可能来源。
recv
是一个系统调用,由于网络消息可能在 TCP/IP 数据包中组合(或分段),因此调用 recv
可能比处理内部消息更昂贵解决碎片和溢出状态的数据缓冲区。
重新考虑每个客户端线程设计
我还建议避免每个客户端线程设计。
一般来说,对于少数客户来说,这可能并不重要。
但是,随着客户端的增加和线程变得更加繁忙,您可能会发现系统在上下文切换上花费的资源多于实际任务。
另一个问题可能是每个线程需要分配的堆栈(Ruby 线程需要 1Mb 或 2Mb,如果我没记错的话)...在最好的情况下,1,000 个客户端将需要超过 1 GB 的仅为堆栈分配内存(我忽略了内核结构数据 table 和其他资源)。
我会考虑使用 EventMachine 或 Iodine(我是 iodine 的作者,所以我有偏见)。
事件设计可以为您节省很多资源。
例如(未经测试):
require 'iodine'
# define the protocol for our service
class ExampleProtocol
@timeout = 10
def on_open
puts "New Connection Accepted."
# this should probably be changed,
# it ignores the possibility of two connections arriving at the same timestamp.
t = Time.now.strftime("%d-%m-%Y %H%M")
file_name = t + '.txt'
@out_file = File.new(file_name, "w+")
# a rolling buffer for fragmented messages
@expecting = 33
@msg = ""
end
def on_message buffer
length = buffer.length
pos = 0
while length >= @expecting
@msg << (buffer[pos, @expecting])
out_file.puts(msg.unpack('H*')[0])
length -= @expecting
pos += @expecting
@expecting = 59
@msg.clear
end
if(length > 0)
@msg << (buffer[pos, length])
@expecting = 59-length
end
end
def on_close
@out_file.close
end
end
# create the service instance
Iodine.listen 12050, ExampleProtocol
# start the service
Iodine.start