How to close a TCP socket? (TCPSocket#close doesn't close it)

Hi, perhaps I miss something but when I close a TCP connection using
TCPSocket#close the connection remains as OS level for long seconds (minutes).

Be reading the doc:

  IO#close

  Closes ios and flushes any pending writes to the operating system. The
  stream is unavailable for any further data operations; an IOError is raised
  if such an attempt is made. I/O streams are automatically closed when they
  are claimed by the garbage collector.

So, does it mean that the TCP connection is *really* terminated when the
garbage collector removes it (some seconds/minutes after calling
TCPSocket#close)?

Unfortunatelly it's not valid for me, I need the capability to close a TCP
connection and open a new one without mantaining two parallel connections.

Any other way to really close a TCP connection at OS level? Thanks.

···

--
Iñaki Baz Castillo <ibc@aliax.net>

Hi, perhaps I miss something but when I close a TCP connection using
TCPSocket#close the connection remains as OS level for long seconds (minutes).

Be reading the doc:

IO#close

Closes ios and flushes any pending writes to the operating system. The
stream is unavailable for any further data operations; an IOError is raised
if such an attempt is made. I/O streams are automatically closed when they
are claimed by the garbage collector.

So, does it mean that the TCP connection is *really* terminated when the
garbage collector removes it (some seconds/minutes after calling
TCPSocket#close)?

The TCP connection is terminated when you call TCPSocket#close.

Unfortunatelly it's not valid for me, I need the capability to close a TCP
connection and open a new one without mantaining two parallel connections.

Any other way to really close a TCP connection at OS level? Thanks.

Why do you need to do that? Your connection is in the TIME_WAIT state,
which is exists for good reasons. There are no parallel connections.

···

On Tue, Jan 12, 2010 at 11:43 AM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

> Hi, perhaps I miss something but when I close a TCP connection using
> TCPSocket#close the connection remains as OS level for long seconds
> (minutes).
>
> Be reading the doc:
>
> IO#close
>
> Closes ios and flushes any pending writes to the operating system. The
> stream is unavailable for any further data operations; an IOError is
> raised if such an attempt is made. I/O streams are automatically closed
> when they are claimed by the garbage collector.
>
> So, does it mean that the TCP connection is *really* terminated when the
> garbage collector removes it (some seconds/minutes after calling
> TCPSocket#close)?

The TCP connection is terminated when you call TCPSocket#close.

Yes, I'm realizing of it right now. There must be some error in my code which
prevents the socket from being closed. I must investigate it.

> Unfortunatelly it's not valid for me, I need the capability to close a
> TCP connection and open a new one without mantaining two parallel
> connections.
>
> Any other way to really close a TCP connection at OS level? Thanks.

Why do you need to do that? Your connection is in the TIME_WAIT state,
which is exists for good reasons. There are no parallel connections.

In my buggy script the connection remains ESTABLISHED, but as I said above it
must be my fault somewhere in the script.

However, by trying just:

  require "socket"
  s = TCPSocket.new(server, port)
  s.close

After "s.close" the connection is ended totally, no TIME_WAIT status.

Thanks a lot.

···

El Martes, 12 de Enero de 2010, Lars Christensen escribió:

On Tue, Jan 12, 2010 at 11:43 AM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

--
Iñaki Baz Castillo <ibc@aliax.net>

Take a look at this tutorial:

http://www.ssfnet.org/Exchange/tcp/tcpTutorialNotes.html

There's a diagram explaining all the states for a TCPSocket, and
explains the reaons for the TIME_WAIT status.

Hope this helps,

Jesus.

···

On Tue, Jan 12, 2010 at 12:26 PM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

El Martes, 12 de Enero de 2010, Lars Christensen escribió:

On Tue, Jan 12, 2010 at 11:43 AM, Iñaki Baz Castillo <ibc@aliax.net> wrote:
> Hi, perhaps I miss something but when I close a TCP connection using
> TCPSocket#close the connection remains as OS level for long seconds
> (minutes).
>
> Be reading the doc:
>
> IO#close
>
> Closes ios and flushes any pending writes to the operating system. The
> stream is unavailable for any further data operations; an IOError is
> raised if such an attempt is made. I/O streams are automatically closed
> when they are claimed by the garbage collector.
>
> So, does it mean that the TCP connection is *really* terminated when the
> garbage collector removes it (some seconds/minutes after calling
> TCPSocket#close)?

The TCP connection is terminated when you call TCPSocket#close.

Yes, I'm realizing of it right now. There must be some error in my code which
prevents the socket from being closed. I must investigate it.

> Unfortunatelly it's not valid for me, I need the capability to close a
> TCP connection and open a new one without mantaining two parallel
> connections.
>
> Any other way to really close a TCP connection at OS level? Thanks.

Why do you need to do that? Your connection is in the TIME_WAIT state,
which is exists for good reasons. There are no parallel connections.

In my buggy script the connection remains ESTABLISHED, but as I said above it
must be my fault somewhere in the script.

However, by trying just:

require "socket"
s = TCPSocket.new(server, port)
s.close

After "s.close" the connection is ended totally, no TIME_WAIT status.

I've found the cause of my issue. My ruby script listens into a pipe and when
a message is received it's sent via the TCPSocket.
Also there is a thread which does @socket.gets("\r\n") to receive the
responses. In this way I can I can send multiple requests to the server
without waiting for the responses. Instead the thread doing "gets" reads the
responses, parses a request identifier and that's all.

But in this case when calling socket.close the connection is not terminated. A
simplified code:

  require "socket"
  @socket = TCPSocket.new(server, port)
  
  Thread.new do
    res = @socket.gets("\r\n")
  end

  @socket.close
  sleep 120

When "@socket.close" is called it's just closed "at Ruby level", this is, I
cannot write/read from it as I get "closed pipe" error (which makes sense).

However the TCP connection remains open at OS level (in ESTABLISHED status as
netstat shows in both the client and server).

But it's really interesting (for me) the following fact:

Without the line @socket.close netstat output shows (during the sleep 120):

  tcp 0 0 192.168.1.10:51112 88.231.79.226:5062 ESTABLISHED 23814/irb
  
But when running the script with @socket.close line then I see:

  tcp 0 0 192.168.1.10:51112 88.231.79.226:5062 ESTABLISHED -

It's like if the Ruby process (irb) has detached itself from the socket (no
process pid in the netstat output for that connection), but the socket remains
open at OS level.

Is it the expected behavior?

Thanks.

···

El Martes, 12 de Enero de 2010, Iñaki Baz Castillo escribió:

> The TCP connection is terminated when you call TCPSocket#close.

Yes, I'm realizing of it right now. There must be some error in my code
which prevents the socket from being closed. I must investigate it.

--
Iñaki Baz Castillo <ibc@aliax.net>

Thanks a lot, but as I've explained I don't see TIME_WAIT, my issue is
different (described in other mail sent right now).

Thanks.

···

El Martes, 12 de Enero de 2010, Jesús Gabriel y Galán escribió:

> After "s.close" the connection is ended totally, no TIME_WAIT status.

Take a look at this tutorial:

http://www.ssfnet.org/Exchange/tcp/tcpTutorialNotes.html

There's a diagram explaining all the states for a TCPSocket, and
explains the reaons for the TIME_WAIT status.

--
Iñaki Baz Castillo <ibc@aliax.net>

Iñaki Baz Castillo wrote:

But in this case when calling socket.close the connection is not
terminated. A
simplified code:

  require "socket"
  @socket = TCPSocket.new(server, port)

  Thread.new do
    res = @socket.gets("\r\n")
  end

  @socket.close
  sleep 120

When I run this code, substituting a 'www.google.com' and port 80, I see
the socket in TIME_WAIT state if I go to another terminal.

This is with
ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux]
built from source, running under Ubuntu Hardy.

It appears to be consistent: i.e. I can ctrl-C out of the program and
start it again, and each time I get another TIME_WAIT socket.

Whilst what you're doing is probably allowed, it's a bit ugly: one
thread is waiting to read data from a socket at the same time as another
closes it.

There is a more graceful way which might be useful. In the writing
thread you can half-close the socket (@socket.close_write). If the other
side notices this and then closes from its side, you can then finish
reading what it sends you and close the read side.

  require "socket"
  @socket = TCPSocket.new('smtp.example.com', 25)

  Thread.new do
    while res = @socket.gets("\r\n")
      STDERR.puts res
    end
    @socket.close_read
  end

  @socket.close_write
  sleep 120

Anyway, going back to your original code: to isolate the behaviour
you've seen, I suggest you set up a ruby server and client pair of
processes and connect between them (preferably using localhost). This
makes it completely standalone and easy to reproduce by others, and
potentially easy to fix if it is in fact a bug. Post your ruby version
and platform too.

Regards,

Brian.

···

--
Posted via http://www.ruby-forum.com/\.

When I run this code, substituting a 'www.google.com' and port 80, I see
the socket in TIME_WAIT state if I go to another terminal.

This is with
ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux]
built from source, running under Ubuntu Hardy.

It appears to be consistent: i.e. I can ctrl-C out of the program and
start it again, and each time I get another TIME_WAIT socket.

I really sorry, I was doing a "grep ESTABLISHED" in my netstat command...
Of course the TIME_WAIT appears after closing the connection :slight_smile:
Sorry.

Whilst what you're doing is probably allowed, it's a bit ugly: one
thread is waiting to read data from a socket at the same time as another
closes it.

There is a more graceful way which might be useful. In the writing
thread you can half-close the socket (@socket.close_write). If the other
side notices this and then closes from its side, you can then finish
reading what it sends you and close the read side.

  require "socket"
  @socket = TCPSocket.new('smtp.example.com', 25)

  Thread.new do
    while res = @socket.gets("\r\n")
      STDERR.puts res
    end
    @socket.close_read
  end

  @socket.close_write
  sleep 120

Yes, this works much better :slight_smile:
Thanks a lot.

···

El Martes, 12 de Enero de 2010, Brian Candler escribió:

--
Iñaki Baz Castillo <ibc@aliax.net>