如何在 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
(它对我来说是一个更大系统的一部分)。可能有一些错误,所以如果你发现它们请评论,我会更正。
希望这对找到它的人有所帮助。