Ruby: TCPServer is not seeing that client TCPSocket has closed - ruby

While running a TCPServer in ruby 2.7.0, I want to see when my client has closed the connection (or is unable to continue reading). However, when I check on the server, I never see that the connection has closed.
I've tried using a bunch of the different ruby socket primitives but nothing seems to work here. I've tried writing to the socket as well in hopes of forcing an error but that doesn't seem to help.
I'm including an example here:
# main.rb
require_relative 'server'
PORT = 9000
server_thread = Server.thread
socket = TCPSocket.open("localhost", PORT)
socket.puts '5'
server_thread.join(1)
socket.close
puts socket.closed?
server_thread.join(2)
# server.rb
require 'socket'
class Server
def self.thread
Thread.new do
server = TCPServer.open(PORT)
while true
server.accept do |socket|
while true
socket.puts '1'
# why doesn't this ever happen?
puts 'closed' if socket.closed?
end
end
end
end
end
end
When running ruby main.rb, this code outputs
true
Whereas I expect it to output:
true
closed

The block that you pass to server.accept is ignored because Socket#accept does not accept a block argument.
When you call socket.close in main.rb, you close the client side of the connection. The server side of the connection will remain open.
You could call IO#read to wait until the client closes the connection.
Thread.new do
server = TCPServer.open(PORT)
loop do
socket = server.accept
socket.read
puts 'client closed connection'
socket.eof? #=> true
socket.close
end
end

The only response provided did not work. Attempting to read from the closed socket did not error like I expected it to.
I have come to the conclusion that what I was asking is simply not possible. You must either:
Send a keep-alive from the client and close when you do not receive it or
Face the consequences of not knowing whether or not your writes have
succeeded.
Personally, I was able to live with 2 since this was for a prototype.

Related

How to tell if a TCP socket has been closed by the client in Ruby?

I've read some things suggesting that because of the design of TCP this might not be possible (such as: Java socket API: How to tell if a connection has been closed?), but I'm trying to find explicit confirmation. I have a basic TCP server that accepts connections, and a client that initiates a connection, sends a message, and then closes the connection. Is there a way for the server to know that the client closed the connection?
I found some suggestions to look into checking the file descriptors for the sockets (source: How to check if a given file descriptor stored in a variable is still valid?), using the kernel select command (source: https://bytes.com/topic/c/answers/866296-detecting-if-file-descriptor-closed) as well as using recv to check if the client returns 0 (source: http://man7.org/linux/man-pages/man2/recv.2.html#RETURN_VALUE), but these do not seem to work, at least not when called by Ruby. To test this, I wrote a basic server and client:
test_server.rb
require 'socket'
require 'fcntl'
TIMEOUT = 5
server = TCPServer.new('localhost', 8080)
puts "Starting server"
loop do
client = server.accept
puts "New client: #{client}"
puts "** before closed #{Time.now.to_i} closed=#{client.closed?}"
result = IO.select([client], nil, nil, TIMEOUT)
puts "select result=#{result}"
fd = client.fcntl(Fcntl::F_GETFD, 0)
puts "client fd=#{fd}"
stuff = client.recv(30)
puts "received '#{stuff}'"
begin
r = client.recv(1)
rescue => e
end
puts "received #{r} nil?=#{r.nil?}"
sleep 3
puts "** after closed #{Time.now.to_i} closed=#{client.closed?}"
result = IO.select([client], nil, nil, TIMEOUT)
puts "select result=#{result}"
fd = client.fcntl(Fcntl::F_GETFD, 0)
puts "client fd=#{fd}"
begin
r = client.recv(1)
rescue => e
end
puts "received #{r} nil?=#{r.nil?}"
puts "done!"
end
test_client.rb
require 'socket'
class Client
def initialize
#socket = tcp_socket
end
def tcp_socket
Thread.current[:socket] = TCPSocket.new("localhost", 8080)
end
def send(s, args={})
puts "sending str '#{s}'"
nbytes = #socket.send(s, 0)
puts "received #{nbytes} bytes"
sleep 1
#socket.close
puts "done at #{Time.now.to_i}: #{#socket.closed?}"
end
end
msg = 'hello world this is my message'
server = Client.new
server.send(msg)
The client sends a 30-byte message, waits 1s, then closes the connection.
The server accepts the connection, calls select and fcntl on it to check its status, receives the message, tries to read 1 more byte, sleeps for 3 seconds, then calls select and fcntl and again tries to read 1 byte. The intent here is to check if anything changes that the server can see before and after the client closed the connection (hence the 3-second sleep). The result I get from running the server and then the client code is:
Starting server
New client: #<TCPSocket:0x00007fa0930f0880>
** before closed 1578005539 closed=false
select result=[[#<TCPSocket:fd 10>], [], []]
client fd=1
received 'hello world this is my message'
received nil?=false
** after closed 1578005543 closed=false
select result=[[#<TCPSocket:fd 10>], [], []]
client fd=1
received nil?=false
done!
Before and after the client closed the connection, select still sees the socket as readable, the underlying file descriptor does not change, and recv returns empty string (It's possible the kernel call is returning 0 as specified in the man-page but Ruby is capturing that, and if so I don't know how to see it.). Thus none of these seem to be a reliable indicator of whether the connection was closed from the other side. Is there something I'm missing?
I have seen some other suggestions to incorporate a regular heartbeat back to the client, but I'm wondering if there's a way to avoid that. Reason is that I'm trying to accommodate a case where the client may be sending a message in several pieces separated by a delay (e.g. 100 bytes at 1 second each byte). If the server sends a heartbeat message in the middle of that operation and listens for an OK, I presume the client has to be listening for the heartbeat as well and send its OK back, separate from the ongoing message send, and in my test case, I can't change the client to do that.
I have seen some other suggestions to incorporate a regular heartbeat back to the client, but I'm wondering if there's a way to avoid that.
A heartbeat (ping) is the only viable solution.
There is no way to reliably know if the connection is live except by trying to send data over the wire.
Since TCP/IP doesn't require any traffic when data isn't being sent (or received), there's no way for the TCP stack (not even in the OS kernel) to know if the connection is "live" without attempting to exchange data over the wire.
Some connections will close gracefully, allowing the TCP stack to recognize that the connection was closed - but this isn't always true (you can read more about "half-open" or "half-closed" connections).
For this reason, all servers implement a timeout / ping mechanism to test for lost connectivity.
I'm trying to accommodate a case where the client may be sending a message in several pieces separated by a delay (e.g. 100 bytes at 1 second each byte)...
Remember that TCP/IP is a stream based protocol, not a message based protocol.
This means that your 100 bytes might arrive fragmented or they might be combined with a previous message.
If you're sending messages (rather than streaming data), you need - by design - to mark message boundaries.
Since these message boundaries must be marked, it becomes relatively easy to add a message type marker (to mark ping/pong messages).
You can observer the WebSocket protocol message format to learn more about message based protocol design using a TCP/IP (streamed) connection.

Ruby Socket Programming Waiting for Client Input

I am trying to implement a small script that will create a server, wait for a client to connect, create a new thread for the client and then process client requests. The first request from the client will be a HELO text and the server must respond with the HELO text followed by IP Address, Port Number and a Student Number. I have got this bit working.
The next request will be any random string and this must not return anything. I have got this bit working.
The last request will be a KILL_SERVICE request which must close the server. I am not sure how to do this. The other issue is that my program will only work with one command at a time. I do not know how to deal with one command and then wait for the next command from the client. My server code is below. Any help would be appreciated.
require 'socket'
port = 8888
puts "Starting Up Server"
server = TCPServer.open(port)
while (client = server.accept)
Thread.start do
input = client.gets
if input.start_with?("HELO")
client.puts "#{input}IP:#{client.peeraddr[2]}\nPort:#{port}\nStudentID:[2]\n"
elsif input == "KILL_SERVICE\n"
client.puts "KILL"
else
puts input
end
end
end
As soon as your code leaves the Thread.start block that thread is considered finished. If you want it to stick around you need to wrap it in a loop:
while (client = server.accept)
Thread.start do
client_running = true
while (client_running)
# ... Your code
end
end
end
Then, if any of those commands should stop the thread, set client_running = false and the loop will exit. To force shut-down the whole thing, exit(0) will end the process.
Update: Added block on how to handle shutting down the server itself, not just the connection.
You need to continue waiting for input from the client after accepting the connection.
while (client = server.accept)
Thread.start do
while input = client.gets
# process input
end
end
end
When you receive KILL_SERVICE you need to close the client using client.close and break out of the while loop using break. Once you've broken out of the while loop, the thread handling that client will exit.

Check if socket-client is still connected

I want to implement a while-loop that runs as long as a Client is connected at my socket.
It's gonna be Thread based so i want to make shure that the Thread gets closed once the Client disconnected.
question: How do I check if the Client is still connected to my socket?
question: Is the Thread already getting closed when connection closes if I startet it like this: Thread.start(socket.accept) do |client| ...
Check this out,
require 'socket'
th=[]
server = TCPServer.open(2000)
loop do
Thread.start(server.accept) do |client|
client.puts "server started.."
# Something
th = Thread.list
th.each do |t|
if t.status == "sleep" # checks all client status
f = t.kill # kills thread if client is disconnected
end
end
end
end
You may kill the thread or you can store the IP addres of clients using that thread in an array and make a compare with that for exact client.(like, t.addr)
I don't have experience programming ruby. If it was .Net, what you pretend is not possible without some kind of keep-alive protocol. If the server does not get data from client after x seconds it assumes the client disconnected. The client must have a timer to send something to the server when not communicating.
server = TCPServer.open 12345
loop {
Thread.start(server.accept) do |client|
...
puts "client is offline!" if client.closed?
...
client.close
end
}
You may check "closed?" in "client".

Ruby socket server thread question: how to send to all clients?

I'm making a TCP socket server(ruby). A thread is created for each connected client. At some point I try to send data to all connected clients. The thread aborts on exception while trying.(ruby 1.8.7)
require 'socket'
# I test it home right now
server = TCPServer.new('localhost', 12345);
while(session = server.accept)
#Here is the thread being created
Thread.new(session) do |s|
while(msg = s.gets)
#Here is the part that causes the error
Thread.list.each { |aThread|
if aThread != Thread.current
#So what I want it to do is to echo the message from one client to all others
#But for some reason it doesn't, and aborts on the following string
aThread.print "#{msg}\0"
end
}
end
end
Thread.abort_on_exception = true
end
What am I doing wrong?
The following PDF discusses socket programming in ruby in detail. It includes asynchronous handling and goes through a functioning demonstration of a small chat program.
IBM guide to ruby socket programming

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.

Resources