2016-04-22 20 views
6

我正在构建一个连接到服务器并等待数据的客户端Ruby库,但也允许用户通过调用方法发送数据。如何在Ruby中创建双向SSL套接字

我用的机制是有初始化一个插口对一类,像这样:

def initialize 
    @pipe_r, @pipe_w = Socket.pair(:UNIX, :STREAM, 0) 
end 

,我允许开发人员调用将数据发送到服务器的方法是这样的:

def send(data) 
    @pipe_w.write(data) 
    @pipe_w.flush 
end 

然后,我有一个单独的线程,其中,I从连接到所述服务器的socket选择一个循环,并从@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 

这个工程很漂亮,只有正常TCPSocket根据示例。我需要使用OpenSSL::SSL::SSLSocket代替,但是按照the IO.select docs

的最佳方式使用IO.select是非阻塞的方法,如read_nonblock,write_nonblock,等以后调用它

[...]

特别是,对于IO类对象(如OpenSSL :: SSL :: SSLSocket),非阻塞方法和IO.select的组合更受欢迎。

根据这一点,我需要调用IO.select非阻塞的方法,而在我的循环我用它之前,所以我可以从2个不同的IO对象选择。

如何使用IO.select使用SSL插座给出的例子是:

begin 
    result = socket.read_nonblock(1024) 
rescue IO::WaitReadable 
    IO.select([socket]) 
    retry 
rescue IO::WaitWritable 
    IO.select(nil, [socket]) 
    retry 
end 

但是只有IO.select使用带有 IO对象这部作品。

我的问题是:鉴于我需要从@pipe_rsocket对象中进行选择,我怎样才能让我的上一个示例使用SSL套接字?

编辑:我试过什么@ steffen-ullrich建议,但无济于事。我能够使我的测试通过使用以下内容:

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 

这看起来并不坏,但任何输入是受欢迎的。

回答

2

我对ruby本身并不熟悉,但是使用基于SSL的套接字使用select的问题。 SSL套接字的行为与TCP套接字的行为不同,因为SSL数据在帧中传输,而不是作为数据流传输,但是流语义应用于套接字接口。

让我们用一个例子说明这一点,首先使用简单的TCP连接:

  • 服务器在单个写入发送1000个字节。
  • 数据将被传送到客户端并放入内核套接字缓冲区。因此,选择将返回数据可用。
  • 客户端应用程序首先只读取100个字节。
  • 其他的900个字节将被保存在内核套接字缓冲区中。 select的下一次调用将再次返回数据可用,因为select会查看内核中的套接字缓冲区。

使用SSL此不同的是:

  • 服务器在单个写入发送1000个字节。然后,SSL堆栈将把这1000个字节加密成单个SSL帧,并将此帧发送给客户端。
  • 该SSL帧将被传送到客户端并放入内核套接字缓冲区。因此,选择将返回数据可用。
  • 现在客户端应用程序只读取100个字节。由于SSL帧包含1000个字节,并且由于需要完整帧来解密数据,因此SSL堆栈将从套接字读取完整帧,而不会在内核套接字缓冲区中留下任何内容。然后它将解密该帧并将所请求的100个字节返回给应用程序。其余解密的900字节将保存在用户空间的SSL堆栈中。
  • 由于内核中的套接字缓冲区为空,因此select的下一个调用将不会返回该数据可用。因此,如果应用程序只处理select,那么现在不会有未读数据,即保存在SSL堆栈中的900个字节。

如何处理这种差异:

  • 一种方式是始终尝试读取至少32768点的数据,因为这是一个SSL帧的最大尺寸。通过这种方式,人们可以确定没有数据仍保留在SSL堆栈中(SSL读取不会通过SSL帧边界进行读取)。
  • 另一种方法是在调用select之前检查SSL堆栈已解密的数据。只有在SSL堆栈中没有数据保存时,才应该调用select。要检查这种“待处理数据”,请使用the pending method
  • 尝试从无阻塞套接字读取更多数据,直到没有更多数据可用。通过这种方式,您可以确保SSL堆栈中的数据尚未挂起。但是请注意,这也会读取底层的TCP套接字,因此可能更喜欢SSL套接字上的数据,而不是其他套接字上的数据(仅在成功选择后读取)。这是你引用的建议,但我更喜欢其他人。
+0

谢谢@斯蒂芬 - 乌尔里希,这是一个非常明确的答复。不幸的是,我在使用select之前试过使用pending方法,我总是得到0字节可用。我也尝试了nonblock_read(32768),这也不是绝招。将尝试更多。 – ostinelli

+0

会接受这个答案,因为它阐明了解决这些问题的正确方法。再次感谢你! – ostinelli