如何在 Ruby 中热重启 TCPServer
热重启(又名复制)是管理员可以在不丢失客户端连接的情况下重新加载应用程序(以及自上次启动后所做的任何新更改)的过程。这对于管理客户期望非常有用,因为应用程序在使用时不需要遭受严重的停机和中断。
我在下面提出的建议可能不是最佳做法,但它正在发挥作用,也许会指导其他人采用类似的解决方案。
命令
我使用一种特殊的编码风格,利用命令表来查找函数及其可访问性。所有命令函数都以cmd 为前缀。我会清理杂项以提高可读性:
def cmdCopyover
#$nCS is the TCPServer object
#$connected holds an array of all users sockets
#--copyover flags that this is a hot reboot.
connected_args = $connected.map do |sock|
sock.close_on_exec = false if sock.respond_to?(:close_on_exec=)
sock.fileno.to_s
end.join(",")
exec('./main.rb', '--copyover', $nCS.fileno.to_s, connected_args)
end
我们传递的是字符串; $nCS.fileno.to_s 为我们提供主 TCPServer 对象的文件描述符,而 connected_args 是每个连接的用户的文件描述符列表,以逗号分隔。当我们重新启动时,ARGV 将是一个包含每个参数的数组:
ARGV[0] == "--copyover"
-
ARGV[1] == "5"(或任何 TCPServer 的文件描述符)
-
ARGV[2] == "6,7,8,9"(例如,假设有 4 个连接的用户)
当您期待时会发生什么(复制)
在正常情况下,我们可能有一个基本服务器(在main.rb 中看起来像这样:
puts "Starting Server"
$connected = Array.new
$nCS = TCPServer.new("127.0.0.1",9999)
begin
while socket = $nCS.accept
# NB: Move this loop to its own function, threadLoop()
Thread.new( socket ) do |sock|
begin
while sock.gets
szIn = $_.chomp
#do something with input.
end
rescue => e
puts "ERROR: Caught error in Client Thread: #{e}"
puts #{e.backtrace.to_s.gsub(",", ",\r\n")}"
sock.write("Sorry, an error has occurred, and you have been disconnected."+EOL+"Please try again later."+EOL)
sock.close
end
end
end
rescue => e
puts "Error: Caught Error in Server Thread: #{e}"
puts "#{e.backtrace.to_s.gsub(",", ",\r\n")}"
exit
end
我们希望将该主循环移动到它自己的函数中以使其可访问——我们重新连接的用户将需要重新插入循环中。
让我们让main.rb 准备好接受热重启:
def threadLoop( socket )
Thread.new( socket ) do |sock|
begin
while sock.gets
szIn = $_.chomp
#do something with input.
end
rescue => e
puts "ERROR: Caught error in Client Thread: #{e}"
puts #{e.backtrace.to_s.gsub(",", ",\r\n")}"
sock.write("Sorry, an error has occurred, and you have been disconnected."+EOL+"Please try again later."+EOL)
sock.close
end
end
end
puts "Starting Server"
$connected = Array.new
if ARGV[0] == '--copyover'
$nCS = TCPServer.for_fd( ARGV[1].to_i )
$nCS.close_on_exec = false if $nCS.respond_to?(:close_on_exec=)
connected_args = ARGV[2]
connected_args.split(/,/).map do |sockfd|
$connected << sockfd
$connected.each {|c| threadLoop( c ) }
else
$nCS = TCPServer.new("127.0.0.1",9999)
$nCS.close_on_exec = false if $nCS.respond_to?(:close_on_exec=)
end
begin
while socket = $nCS.accept
threadLoop( socket )
end
rescue => e
puts "Error: Caught Error in Server Thread: #{e}"
puts "#{e.backtrace.to_s.gsub(",", ",\r\n")}"
exit
end
警告
我的实际使用要复杂得多,所以我尽量把所有的垃圾都去掉;然而,当我在这里结束时,我意识到你可能没有$connected(它对我来说是一个更大系统的一部分)。可能有一些错误,所以如果你发现它们请评论,我会更正。
希望这对找到它的人有所帮助。