Understanding IO.select when reading socket in Ruby
1) IO.select
takes a set of sockets and waits until it's possible to read or write with them (or if error happens). It returns sockets event happened with.
2) array contains sockets that are checked for events. In your case you specify only sockets for reading.
3) IO.select
returns an array of arrays of sockets. Element 0 contains sockets you can read from, element 1 - sockets you can write to and element 2 - sockets with errors.
After getting list of sockets you can read the data.
4) yes, recv_nonblock
argument is size in byte. Note that size of data actually being read may be less than 1024, in this case you may need to repeat select
(if actual data matters for you).
Ruby - Character-wise IO.select
You can set stdin to raw mode (taken from this answer):
begin
state = `stty -g`
`stty raw -echo -icanon isig`
loop do
rd, _, _ = IO.select([$stdin])
handle_keypress(rd[0].getc)
end
ensure
`stty #{state}`
end
IO#getc
returns a single character from stdin. Another option is IO#read_nonblock
to read all available data.
Socket.read() won't block in Ruby
read
will block if there is no data, but not at EOF. The IO#read
docs say:
When this method is called at end of file, it returns
nil
or""
, depending on length:read
,read(nil)
, andread(0)
return""
,read(positive_integer)
returnsnil
.
Since calling read
on EOF doesn’t block, select
will return the IO as readable straight away.
In your code the first call to read
will block until all the data is read from the connection (i.e. the other end has closed it). From then it will be at EOF, so select
will return it as ready, and read
will return an empty string immediately.
Sockets in Ruby, how can I asynchronously read and write data through a Socket
Based on Casper's recommendation in the question comments, I changed the proc to use IO.select
. It worked as a breese.
The first thing to know is that attempting to read the socket (@socket.recv
) when there is no data in its buffer will make the socket "hang" waiting for a new message from the server. This has the the annoying side effect of preventing us from writing to the socket (via separated thread).
So to fix this, the IO.select
call would try to read the socked for 2 seconds (the 2 used as last parameter below). When the 2s elapse without data, IO.select
returns nil
which allowed me to not call @socket.recv
when we know it would "hang".
A great side effect is that my while true
does not kill the client when there is no data, as IO.select
takes 2s to timeout on each loop.
This is the end result:
@socket = TCPSocket.new(localhost, 1234)
def read_data_loop
while true
while IO.select([@socket], nil, nil, 2) && (line = @socket.recv(50))
p line
end
end
end
@buffer_thread = Thread.new { read_data_loop }
@socket.write("send me something!!")
sleep(10)
@buffer_thread.exit
This made the background thread write to the console:
server response
hi there
as these were two messages I had set up on the server.
IO#read blocks on non-blocking socket?
You were right to expect the answer "IO#read does not respect the flags set on the underlying file descriptor":
ruby 1.9.3 IO#read
Note that this method behaves like fread() function in C. If you need
the behavior like read(2) system call, consider readpartial,
read_nonblock and sysread.
I appreciate that you're using 1.8, but
- you're seeing the process stuck in
io_fread
, and IO#read_nonblock
is available in 1.8 as well.
How to read from a TCPServer socket in ruby using read, readpartial and read_nonblock
It's done in unicorn here
https://github.com/defunkt/unicorn/blob/master/lib/unicorn/http_request.rb#L69-L71
There is add_parse
method(read the comments above methods)
https://github.com/defunkt/unicorn/blob/master/ext/unicorn_http/unicorn_http.rl#L760-L778
Also take a look at some explanations here http://www.ruby-forum.com/topic/2267632#1014288
Here is your working code using http_parser.rb https://gist.github.com/4136962
gem install http_parser.rb
require 'socket'
require "http/parser"
READ_CHUNK = 1024 * 4
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
addr = Socket.pack_sockaddr_in(9799, '127.0.0.1')
socket.bind(addr)
socket.listen(Socket::SOMAXCONN)
socket.setsockopt(:SOCKET, :REUSEADDR, true)
puts "Server is listening on port = 9799"
loop do
connection, addr_info = socket.accept
parser = Http::Parser.new
begin
data = connection.readpartial(READ_CHUNK)
puts "Buffer = #{data}"
parser << data
end until parser.headers
connection.write("HTTP/1.1 200 \r\n")
connection.write("Content-Type: text/html\r\n")
connection.write("Status 200 \r\n")
connection.write("Connection: close \r\n")
connection.write("\r\n\r\n")
connection.write("Hello World \r\n")
connection.close
end
Related Topics
Rails Generate Has_Many Association
Activerecord Objects in Hashes Aren't Garbage Collected -- a Bug or a Sort of Caching Feature
Error Installing Any Ruby Version with Rvm on Osx
Rails 5.2.0 with Ruby 2.5.1 Console - 'Warning:' 'Already' Initialized Constant Fileutils::Version
What Are the Ruby's Object#Taint and Object#Trust Methods
Rails Development Server Is Slow and Takes a Long Time to Load a Simple Page
How to Filter an Array of Hashes to Get Only the Keys in Another Array
Ubuntu 12 - How to Install Ruby and Rails Correctly
Ruby on Rails, Could Not Find a Valid Gem 'Rails'
Rails Console Running Incredibly Slowly When Editing Text
How to Do Standard Deviation in Ruby
Respond_To' VS. 'Respond_To_Missing'
How to Get the Width of Terminal Window in Ruby
Can't Use Compass After Installing It
Instance and Class Variables in Rails Controller
Best Way to Generate Order Numbers for an Online Store