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
Ruby: Yield Block from a Block
Sort a List of Objects by Using Their Attributes in Ruby
Ruby Gems Won't Load Even Though Installed
Problem with Rspec Test, Undefined Method 'Post'
2 Gems Need Different Versions of the Same Dependency
How to Show Error Message on Rails Views
Rails Render of Partial and Layout in Controller
What Are All the "Conventions" for Ruby on Rails
Pg_Config, Ruby Pg, Postgresql 9.0 Problem After Upgrade, Centos 5
Need Help Maximizing 3 Factors in Multiple, Similar Objects and Ordering Appropriately
How to Delete Some Folders of Nokogiri and Capybara-Webkit Inside of My Rvm Gemset
Ruby on Rails Add a Column After a Specific Column Name