Ruby Tcpsocket: Find Out How Much Data Is Available

Ruby TCPSocket: Find out how much data is available

The standard library io/wait might be useful here. Requring it gives stream-based I/O (sockets and pipes) some new methods, among which is ready?. According to the documentation, ready? returns non-nil if there are bytes available without blocking. It just so happens that the non-nil value it returns it the number of bytes that are available in MRI.

Here's an example which creates a dumb little socket server, and then connects to it with a client. The server just sends "foo" and then closes the connection. The client waits a little bit to give the server time to send, and then prints how many bytes are avaiable for reading. The interesting stuff for you is in the client:

require 'socket'
require 'io/wait'

# Server

server_socket = TCPServer.new('localhost', 0)
port = server_socket.addr[1]
Thread.new do
session = server_socket.accept
sleep 0.5
session.puts "foo"
session.close
end

# Client

client_socket = TCPSocket.new('localhost', port)
puts client_socket.ready? # => nil
sleep 1
puts client_socket.ready? # => 4

Don't use that server code in anything real. It's deliberately retarded in order to keep the example simple.

Note: According to the Pickaxe book, io/wait is only available if "FIONREAD feature in ioctl(2)". Which it is in Linux. I don't know about Windows & others.

Problems receiving large amount of data through ruby TCP Socket

It's badly documented in the Ruby docs, but I think TCPSocket#recv actually just calls the recv system call. That one (see man 2 recv) reads a number of bytes from the stream that is determined by the kernel, though never more than the application specifies. To receive a larger "message", you will need to call it in a loop.

But there is an easier way: because TCPSocket indirectly inherits from the IO class, you get all of its methods for free, including IO#read which does read as many bytes as you specify (if possible).

You wil also need to implement a way to delimit your messages:

  • use fixed-length messages
  • send the length of the message up front in a (fixed-size) header
  • use some kind of terminator, e.g. a NULL byte

Read data both ways TCPServer Ruby

IO#gets is a blocking call. It waits for either a new line from the underlying I/O stream, or the end of the stream. (in which case it returns nil)

In server.rb you have

while line = session.gets
puts "Inside while loop"
test << line
end

session.gets reads one line from your client, prints some debug info and appends the line to test. It then attempts to read another line from the client.

Your client.rb however never sends a seconds line, nor does it close the stream. It sends a single line:

socket.puts "Sending data.." # Send data to server

and then waits for a response:

while(line = socket.gets)
puts line
end

which never comes because the server is sitting in the while loop, waiting for more data from the client.

You can solve this by calling close_write after all data has been sent:

socket.puts "Sending data.." # Send data to server
socket.close_write # Close socket for further writing

Calling close_write instead of close allows you to still read from the socket. It will also cause the server's session.gets to return nil, so it can get out of its loop.

Is there a way to be notified of new data becoming available in an SSLSocket (Ruby)?

I suggest two way to achieve it.

1) Using Kernel.select or IO.select method (both are the same):

require 'socket'
require 'openssl'

s = TCPSocket.new(host, prot)
ssl = OpenSSL::SSL::SSLSocket.new(s)
ssl.connect

t = Thread.new do
loop do
sr, sw = IO.select [ssl]
puts sr.first.readline
puts '...'
end
end

puts 'start reading'
t.join # join the main thread

The IO.select waits until some data arrived, without the busy loop. The benefit of this solution, it's only uses the standard Ruby library.

2) Using EventMachine library:

require 'eventmachine'

module Client

def post_init
start_tls
end

def receive_data data
# include callback code
puts data
puts '...'
end

end

EM.run do
# start event loop
EM.connect 'localhost', 9000, Client
end

EventMachine, according to the documentation, is an event-driven I/O using the Reactor pattern.

The EventMachine has all you need out of the box. The reactor is implemented in C++ and the thread model is outside the Ruby GIL (Global Interpreter Lock) which makes the library extremely fast.

I have been using it on production for a while and works great!

The both approach will work as you asking for, so I would recommend to benchmark them and see which one fits best to your needs.

How to TCPServer receive data from http request in Ruby?

Something like this would read a greeting to a user, then wait for user input. This example simply echoes the input back on the server side.

server = TCPServer.open(8000)
socket = server.accept
socket.puts 'Hello!' # Prints Hello! to the client
while line = socket.gets
puts line # Prints whatever the client enters on the server's output
end


Related Topics



Leave a reply



Submit