I am building a client Ruby library that connects to a server and waits for data, but also allows users to send data by calling a method.
The mechanism I use is to have a class that initializes a socket pair, like so:
def initialize
@pipe_r, @pipe_w = Socket.pair(:UNIX, :STREAM, 0)
end
The method that I allow developers to call to send data to the server looks like this:
def send(data)
@pipe_w.write(data)
@pipe_w.flush
end
Then I have a loop in a separate thread, where I select from a socket
connected to the server and from the @pipe_r
:
def socket_loop
Thread.new do
socket = TCPSocket.new(host, port)
loop do
ready = IO.select([socket, @pipe_r])
if ready[0].include?(@pipe_r)
data_to_send = @pipe_r.read_nonblock(1024)
socket.write(data_to_send)
end
if ready[0].include?(socket)
data_received = socket.read_nonblock(1024)
h2 << data_received
break if socket.nil? || socket.closed? || socket.eof?
end
end
end
end
This works beautifully, but only with a normal TCPSocket
as per the example. I need to use an OpenSSL::SSL::SSLSocket
instead, however as per the IO.select docs:
The best way to use IO.select is invoking it after nonblocking methods such as read_nonblock, write_nonblock, etc.
[...]
Especially, the combination of nonblocking methods and IO.select is preferred for IO like objects such as OpenSSL::SSL::SSLSocket.
According to this, I need to call IO.select
after nonblocking methods, while in my loop I use it before so I can select from 2 different IO objects.
The given example on how to use IO.select
with an SSL socket is:
begin
result = socket.read_nonblock(1024)
rescue IO::WaitReadable
IO.select([socket])
retry
rescue IO::WaitWritable
IO.select(nil, [socket])
retry
end
However this works only if IO.select
is used with a single IO object.
My question is: how can I make my previous example work with an SSL socket, given that I need to select from both the @pipe_r
and the socket
objects?
EDIT: I've tried what @steffen-ullrich suggested, however to no avail. I was able to make my tests pass using the following:
loop do
begin
data_to_send = @pipe_r.read_nonblock(1024)
socket.write(data_to_send)
rescue IO::WaitReadable, IO::WaitWritable
end
begin
data_received = socket.read_nonblock(1024)
h2 << data_received
break if socket.nil? || socket.closed? || socket.eof?
rescue IO::WaitReadable
IO.select([socket, @pipe_r])
rescue IO::WaitWritable
IO.select([@pipe_r], [socket])
end
end
This doesn't look so bad, but any input is welcome.
I'm not familiar with ruby itself, but with the problems of using select with SSL based sockets. SSL sockets behave differently to TCP sockets since SSL data are transferred in frames and not as a data stream, but nevertheless stream semantics are applied to the socket interface.
Let's explain this with an example, first using a simple TCP connection:
With SSL this is different:
How to deal with this difference:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With