Socket Question - close

If you have two sockets connected to eachother. Let's say...
   a_socket and b_socket

And you tell a_socket to close, why does b_socket not know it is closed?

a_socket.close
b_socket.closed? => false
b_socket.puts "test"
Errno::EINVAL: Invalid argument
        from (irb):4:in `write'
        from (irb):4:in `puts'
        from (irb):4

Am I missing something. Thanks,

Zach

Zach Dennis schrieb:
Zach Dennis wrote:

If you have two sockets connected to eachother. Let's say...
   a_socket and b_socket

And you tell a_socket to close, why does b_socket not know it is closed?

Because there might be some data left in b_socket's input buffer:

a_socket.puts 'Hello world!'
a_socket.close

If b_socket.closed? would be true now, b_socket.gets wouldn't return
"Hello world!", which would be weird. After all, the underlying file
descriptor is still open.

Greets,
Markus

Zach Dennis <zdennis@mktec.com> writes:

If you have two sockets connected to eachother. Let's say...
   a_socket and b_socket

And you tell a_socket to close, why does b_socket not know it is closed?

a_socket.close
b_socket.closed? => false

closed? is true only if you have called close() previously, i.e. if
you have closed the socket locally.

If the remote end closes its side, closed? would still return false if
you haven't called close() yet.

Detecting if the remote end has closed its side is not that
straight-forward.

Consider the following cases with TCP connection:

1. Your OS receives the FIN packet generated when the remote end does
a close(). select() will say that there is data to be read (the FIN
packet), and when you do a read, it will return with 0 data (since the
FIN packet is a transport-level business and does not contain
app-level data). if you try to write to such socket, you'll get EPIPE.

2. No FIN packet received because, say, the cable was hacked with an
axe. If you try to do a read(), read() will block until TCP times out
(2 hours?), same story with write(). That's because it is not possible
to determine if the cable has been chopped from software; for all the
TCP stack knows, it could have been just a network delay when it
doesn't immediately get any reply.

There is no rescuing for case #2, but case #1 is simply BSD socket
semantic. The BSD socket API does not have a mechanism to proactively
see if a FIN has been received. The mechanism must be triggered from
the calling app.

There may be mistakes in the above description as TCP is quite complex
and I don't profess full understanding of its intricacies. But I think
the broad idea is correct.

YS.

Yohanes Santoso <ysantoso-rubytalk@dessyku.is-a-geek.org> writes:

Detecting if the remote end has closed its side is not that
straight-forward.

Let me add to the above statement.

It is not straight forward for TCP connection. Other transport-level
protocol may have other mechanism to reliably detect and indicate
connection termination.

Due to difficulty in detecting connection termination in TCP,
app-level protocols running on top of TCP often has their own
mechanisms:

1. Implicit termination signal, e.g. HTTP/1.0: you send a request, the
   server returns the length of a response and then the response
   itself. Both the client and server knows that after the response is
   done, the connection is considered terminated.

2. Explicit termination signal, e.g. FTP: the client must tell the
   server that the connection is going to be terminated immediately.

3. Timed termination signal: if there is no activity for a certain
   duration, the connection is considered terminated. Usually used in
   app-level time-out mechanism.

YS.

Yohanes Santoso wrote:

It is not straight forward for TCP connection. Other transport-level
protocol may have other mechanism to reliably detect and indicate
connection termination.

I use some undocumented side-effects to detect if the remote has closed the connection. On a TCPSocket, I find that select will immediately return the socket once the remote has closed, but socket#gets will return nil. In any valid transmit condition, if select returns the socket, then gets will return valid data.

A simple reader loop, then, might look like this:

···

------------------------------------------------------------
sock = TCPSocket.new(server, port)
while true
  if select( [sock], nil, nil, 0 )
    data = sock.gets
    break unless data
    puts data
  end
  sleep 0.1
end

puts "Remote closed connection."
sock.close
------------------------------------------------------------

I'm not a Ruby expert by any means. I'm posting this for two reasons:

1. It seems to work for me and may help the OP.

2. I'm relying on undocumented behavior, and don't know of a good way to robustly test this. I'm curious to know if anyone is aware of problems with my solution, and to hear any ideas on how to unit test this.

Cheers,

-dB

--
David Brady
ruby-talk@shinybit.com
I'm having a really surreal day... OR AM I?

David Brady <ruby_talk@shinybit.com> writes:

Yohanes Santoso wrote:

It is not straight forward for TCP connection. Other transport-level
protocol may have other mechanism to reliably detect and indicate
connection termination.

I use some undocumented side-effects to detect if the remote has
closed the connection. On a TCPSocket, I find that select will
immediately return the socket once the remote has closed, but
socket#gets will return nil. In any valid transmit condition, if
select returns the socket, then gets will return valid data.

This is a documented behaviour. I covered this in my previous post:

<previous post>
1. Your OS receives the FIN packet generated when the remote end does
a close(). select() will say that there is data to be read (the FIN
packet), and when you do a read, it will return with 0 data (since the
FIN packet is a transport-level business and does not contain
app-level data). if you try to write to such socket, you'll get EPIPE.
</previous post>

Basically, you don't have to select(), just a read() is enough to read
in the FIN packet and trigger the TCP stack to realise the socket has
been closed by the other side. The select() is there to emphasize
there is data to be read, although that data is not app-level
data. Each time you do a non-zero read() and got a nil, then it means
the other side has closed down.

So, don't worry about using this. Use this liberally :slight_smile:

YS.

Thank you all for you insight and input into this. This has been a great help into my understanding of how sockets, eg, tcp sockets, work.

Zach