Recovering from a broken TCP socket in Ruby when in gets()

╄→尐↘猪︶ㄣ 提交于 2019-12-30 06:26:49

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!