我用Ruby编写了小UDP服务器:
def listen
puts "Started UDP server on #{@port}..."
Socket.udp_server_loop(@port) do |message, message_source|
puts "Got \"#{message}\" from #{message_source}"
handle_incoming_message(message)
end
end
我在一个单独的线程中启动它:
thread = Thread.new { listen }
有没有一种方法可以从线程外部优雅地停止
udp_server_loop
而不只是杀死它(thread.kill
)?我也不想通过接收任何UDP消息从内部停止它。 udp_server_loop
可能不是适合我的工具吗?
最佳答案
我认为您无法使用udp_server_loop
做到这一点(尽管您也许可以使用它使用的某些方法)。您将不得不在自己的循环中调用 IO::select
,并用某种信号通知它退出,并通过某种方式唤醒线程,这样就不必发送数据包来停止它了。
一种简单的方法是使用select
的timeout选项和一个变量来设置,以指示您希望线程结束,例如:
@halt_loop = false
def listen
puts "Started UDP server on #{@port}..."
sockets = Socket.udp_server_sockets(@port)
loop do
readable, _, _ = IO.select(sockets, nil, nil, 1) # timeout 1 sec
break if @halt_loop
next unless readable # select returns nil on timeout
Socket.udp_server_recv(readable) do |message, message_source|
puts "Got \"#{message}\" from #{message_source}"
handle_incoming_message(message)
end
end
end
然后,当您要停止线程时,可以将
@halt_loop
设置为true。不利的一面是它正在有效地轮询。如果减少超时,则可能在空循环上执行更多工作,而如果增加超时,则在停止线程时必须等待更长的时间。
另一个稍微复杂一点的解决方案是使用
pipe
并使select与套接字一起监听。然后,您可以直接发出信号以完成select
并退出线程。@read, @write = IO.pipe
@halt_loop = false
def listen
puts "Started UDP server on #{@port}..."
sockets = Socket.udp_server_sockets(@port)
sockets << @read
loop do
readable, _, _ = IO.select(sockets)
break if @halt_loop
readable.delete @read
Socket.udp_server_recv(readable) do |message, message_source|
puts "Got \"#{message}\" from #{message_source}"
handle_incoming_message(message)
end
end
end
def end_loop
@halt_loop = true
@write.puts "STOP!"
end
要退出线程,您只需要调用
end_loop
即可,该命令会设置@halt_loop
标志,然后将其写入管道,使另一端可读,并使另一个线程从select
返回。您可以让此代码检查可读的
IO
,如果其中之一是管道的读取端而不是使用变量,则退出,但至少在Linux上存在潜在的错误,其中select调用可能返回文件描述符为实际不可读时可读。我不知道Ruby是否可以解决这个问题,所以安全胜过遗憾。另外,在将管道传递给
readable
之前,请确保从udp_server_recv
数组中删除管道。它不是套接字,如果不这样做,将导致异常。这种技术的缺点是管道“在所有平台上都不可用”。
关于ruby - 如何从外部停止udp_server_loop,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57575662/