简体   繁体   中英

Ruby TCPSocket doesn't notice it when server is killed

I've this ruby code that connects to a TCP server (namely, netcat). It loops 20 times, and sends "ABCD ". If I kill netcat, it takes TWO iterations of the loop for an exception to be triggered. On the first loop after netcat is killed, no exception is triggered, and "send" reports that 5 bytes have been correctly written... Which in the end is not true, since of course the server never received them.

Is there a way to work around this issue ? Right now I'm losing data : since I think it's been correctly transfered, I'm not replaying it.

#!/usr/bin/env ruby
require 'rubygems'
require 'socket'

sock = TCPSocket.new('192.168.0.10', 5443)
sock.sync = true

20.times do
  sleep 2
  begin
    count = sock.write("ABCD ")
    puts "Wrote #{count} bytes"
  rescue Exception => myException
    puts "Exception rescued : #{myException}"
  end
end

When you're sending data your blocking call will return when the data is written to the TCP output buffer. It would only block if the buffer was full, waiting for the server to acknowledge receipt of previous data that was sent.

Once this data is in the buffer, the network drivers try to send the data. If the connection is lost, on the second attempt to write, your application discovers the broken state of the connection.

Also, how does the connection close? Is the server actively closing the connection? In which case client socket would be notified at its next socket call. Or has it crashed? Or perhaps there's a network fault which means you can no longer communicate.

Discovering a broken connection only occurs when you try to send or receive data over the socket. This is different from having the connection actively closed. You simply can't determine if the connection is still alive without doing something with it.

So try doing sock.recv(0) after the write - if the socket has failed this would raise " Errno::ECONNRESET: Connection reset by peer - recvfrom(2) ". You could also try sock.sendmsg "", 0 (not sock.write, or sock.send), and this would report a " Errno::EPIPE: Broken pipe - sendmsg(2) ".

Even if you got your hands on the TCP packets and get acknowledgement that the data had been received at the other end, there's still no guarantee that the server will have processed this data - it might in its input buffer but not yet processed.

All of this might help identify a broken connection earlier, but it still won't guarantee that the data was received and processed by the server. The only sure way to know that the application has processed your message is with an application level response.

I tried without the sleep function (just to make sure it wasn't putting on hold anything) and still no luck:

#!/usr/bin/env ruby
require 'rubygems'
require 'socket'
require 'activesupport' # Fixnum.seconds

sock = TCPSocket.new('127.0.0.1', 5443)
sock.sync = true

will_restart_at = Time.now + 2.seconds
should_continue = true

while should_continue
  if will_restart_at <= Time.now
    will_restart_at = Time.now + 2.seconds
    begin
      count = sock.write("ABCD ")
      puts "Wrote #{count} bytes"
    rescue Exception => myException
      puts "Exception rescued : #{myException}"
      should_continue = false
    end
  end
end

I analyzed with Wireshark and the two solutions are exactly behaving identically.

I think (and can't be sure) that until you actually call your_socket.write (which will not fail as the socket is still opened because you weren't probing for its possible destruction), the socket won't raise any error.

I tried to simulate this with nginx and manual TCP sockets. And look at that:

irb> sock = TCPSocket.new('127.0.0.1', 80)
=> #<TCPSocket:0xb743b824>
irb> sock.write("salut")
=> 5
irb> sock.read
=> "<html>\r\n<head><title>400 Bad Request</title></head>\r\n<body>\r\n</body>\r\n</html>\r\n"

# Here, I kill nginx

irb> sock.write("salut")
=> 5
irb> sock.read
=> ""
irb> sock.write("salut")
Errno::EPIPE: Broken pipe

So what's the conclusion from here? Unless you're actually expecting some data from the server, you're screwed to detect that you've lost the connection :)

To detect a gracefully close, you'll have to read from the socket - read returning 0 indicates the socket has closed.

If you do need know if data got sent successfully though, there's no way other than implementing ACKs of the data at the application level.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM