问题
I'm reading lines of input on a TCP socket, similar to this:
class Bla
def getcmd
@sock.gets unless @sock.closed?
end
def start
srv = TCPServer.new(5000)
@sock = srv.accept
while ! @sock.closed?
ans = getcmd
end
end
end
If the endpoint terminates the connection while getline() is running then gets() hangs.
How can I work around this? Is it necessary to do non-blocking or timed I/O?
回答1:
You can use select to see whether you can safely gets from the socket, see following implementation of a TCPServer using this technique.
require 'socket'
host, port = 'localhost', 7000
TCPServer.open(host, port) do |server|
while client = server.accept
readfds = true
got = nil
begin
readfds, writefds, exceptfds = select([client], nil, nil, 0.1)
p :r => readfds, :w => writefds, :e => exceptfds
if readfds
got = client.gets
p got
end
end while got
end
end
And here a client that tries to break the server:
require 'socket'
host, port = 'localhost', 7000
TCPSocket.open(host, port) do |socket|
socket.puts "Hey there"
socket.write 'he'
socket.flush
socket.close
end
回答2:
The IO#closed? returns true when both reader and writer are closed. In your case, the @sock.gets returns nil, and then you call the getcmd again, and this runs in a never ending loop. You can either use select, or close the socket when gets returns nil.
回答3:
I recommend using readpartial to read from your socket and also catching peer resets:
while true
sockets_ready = select(@sockets, nil, nil, nil)
if sockets_ready != nil
sockets_ready[0].each do |socket|
begin
if (socket == @server_socket)
# puts "Connection accepted!"
@sockets << @server_socket.accept
else
# Received something on a client socket
if socket.eof?
# puts "Disconnect!"
socket.close
@sockets.delete(socket)
else
data = ""
recv_length = 256
while (tmp = socket.readpartial(recv_length))
data += tmp
break if (!socket.ready?)
end
listen socket, data
end
end
rescue Exception => exception
case exception
when Errno::ECONNRESET,Errno::ECONNABORTED,Errno::ETIMEDOUT
# puts "Socket: #{exception.class}"
@sockets.delete(socket)
else
raise exception
end
end
end
end
end
This code borrows heavily from some nice IBM code by M. Tim Jones. Note that @server_socket is initialized by:
@server_socket = TCPServer.open(port)
@sockets is just an array of sockets.
回答4:
I simply pgrep "ruby" to find the pid, and kill -9 the pid and restart.
回答5:
If you believe the rdoc for ruby sockets, they don't implement gets
. This leads me to believe gets is being provided by a higher level of abstraction (maybe the IO libraries?) and probably isn't aware of socket-specific things like 'connection closed.'
Try using recvfrom instead of gets
来源:https://stackoverflow.com/questions/61675/recovering-from-a-broken-tcp-socket-in-ruby-when-in-gets