如何在Ruby中创建双向SSL套接字

ost*_*lli 6 ruby sockets openssl bidirectional two-way

我正在构建一个客户端Ruby库,它连接到服务器并等待数据,但也允许用户通过调用方法来发送数据.

我使用的机制是有一个初始化套接字对的类,如下所示:

def initialize
  @pipe_r, @pipe_w = Socket.pair(:UNIX, :STREAM, 0)
end
Run Code Online (Sandbox Code Playgroud)

我允许开发人员调用以向服务器发送数据的方法如下所示:

def send(data)
  @pipe_w.write(data)
  @pipe_w.flush
end
Run Code Online (Sandbox Code Playgroud)

然后我在一个单独的线程中有一个循环,我从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
Run Code Online (Sandbox Code Playgroud)

这样可以很好地工作,只能TCPSocket按照例子的正常情况进行.我需要使用一个OpenSSL::SSL::SSLSocket,但是根据IO.select文档:

使用IO.select的最佳方法是在非阻塞方法(如read_nonblock,write_nonblock等)之后调用它.

[...]

特别是,非阻塞方法和IO.select的组合对于像OpenSSL :: SSL :: SSLSocket这样的IO对象是首选的.

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

有关如何使用IO.selectSSL套接字的给定示例是:

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

但是,这仅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
Run Code Online (Sandbox Code Playgroud)

这看起来并不那么糟糕,但欢迎任何输入.

Ste*_*ich 4

我不熟悉 ruby​​ 本身,但不熟悉将 select 与基于 SSL 的套接字一起使用的问题。SSL 套接字的行为与 TCP 套接字不同,因为 SSL 数据以帧形式传输,而不是作为数据流传输,但流语义仍然应用于套接字接口。

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

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

对于 SSL,情况有所不同:

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

如何处理这种差异:

  • 一种方法是始终尝试读取至少 32768 个数据,因为这是 SSL 帧的最大大小。这样一来,就可以确保 SSL 堆栈中仍然没有保留任何数据(SSL 读取不会越过 SSL 帧边界进行读取)。
  • 另一种方法是在调用 select 之前检查 SSL 堆栈中是否有已解密的数据。仅当 SSL 堆栈中未保存任何数据时,才应调用 select。要检查此类“待处理数据”,请使用待处理方法
  • 尝试从非阻塞套接字读取更多数据,直到没有更多数据可用。这样您就可以确保 SSL 堆栈中没有待处理的数据。但请注意,这也会在底层 TCP 套接字上进行读取,因此与其他套接字上的数据(仅在成功选择后才读取)相比,可能更喜欢 SSL 套接字上的数据。这是您引用的建议,但我更喜欢其他建议。