I'm working on building a custom client on top of Ruby's SSLSocket
. In order to receive data, I've been using the read
and read_nonblock
methods provided by the OpenSSL::Buffering
module.
I'm now trying to take what I have so far, and make it so that I can define a callback (via a user-defined block) that will be run when messages are received. It looks like I basically need to implement something alone these lines:
thread = Thread.new do
while !socket.closed?
while (data = socket.read_nonblock(1024) rescue nil)
@buffer << data
end
sleep 0.1
# ... parse full messages from @buffer & deliver to callbacks ...
end
end
thread.run
The problem I have with this approach is that it's not truly event-driven, and there can be up to a 100ms delay since the data was actually available. Sure, I could change the sleep time, but it just feels a bit hack-ish.
Is there a better approach I could be using for this? If not, what concerns should I have should I decide to implement a shorter/faster loop (e.g.: sleep 0.01
)?
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.