2016-04-22 24 views
6

Tôi đang xây dựng thư viện Ruby của máy khách kết nối với máy chủ và chờ dữ liệu, nhưng cũng cho phép người dùng gửi dữ liệu bằng cách gọi một phương thức.Làm cách nào để tạo ổ cắm SSL hai chiều trong Ruby

Cơ chế tôi sử dụng là phải có một lớp học mà khởi một cặp ổ cắm, như vậy:

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

Phương pháp mà tôi cho phép các nhà phát triển để gọi để gửi dữ liệu đến máy chủ trông như thế này:

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

Sau đó, tôi có một vòng lặp trong một thread riêng biệt, nơi tôi chọn từ một socket kết nối với máy chủ và từ @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 

Công trình này đẹp, chỉ nhưng chỉ với một ví dụ bình thường TCPSocket. Tôi cần phải sử dụng một OpenSSL::SSL::SSLSocket thay vào đó, tuy nhiên theo the IO.select docs:

Cách tốt nhất để sử dụng IO.select là cách gọi nó sau khi phương pháp nonblocking như read_nonblock, write_nonblock vv

[...]

Đặc biệt, kết hợp các phương pháp không chặn và IO.select được ưu tiên cho IO như các đối tượng như OpenSSL :: SSL :: SSLSocket.

Theo điều này, tôi cần phải gọi IO.selectsau phương pháp nonblocking, trong khi trong vòng lặp của tôi, tôi sử dụng nó trước vì vậy tôi có thể chọn từ 2 đối tượng IO khác nhau.

Ví dụ được đưa về cách sử dụng IO.select với một ổ cắm SSL là:

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

Tuy nhiên việc này chỉ khi IO.select được sử dụng với một đơn đối tượng IO .

Câu hỏi của tôi là: làm cách nào để làm ví dụ trước của tôi hoạt động với ổ cắm SSL, cho rằng tôi cần chọn từ cả hai đối tượng @pipe_rsocket?

EDIT: Tôi đã thử những gì @ steffen-ullrich đề xuất, tuy nhiên không có kết quả. Tôi đã có thể thực hiện các thử nghiệm của mình bằng cách sử dụng các mục sau:

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 

Điều này không quá tệ, nhưng mọi đầu vào đều được chào đón.

Trả lời

2

Tôi không quen với ruby, nhưng với các vấn đề khi sử dụng lựa chọn với ổ cắm dựa trên SSL. Các cổng SSL hoạt động khác với các cổng TCP vì dữ liệu SSL được truyền trong các khung và không phải là luồng dữ liệu, nhưng các luồng ngữ nghĩa vẫn được áp dụng cho giao diện socket.

Hãy giải thích điều này với một ví dụ, đầu tiên sử dụng một kết nối TCP đơn giản:

  • Máy chủ gửi 1000 byte trong một ghi duy nhất.
  • Dữ liệu sẽ được gửi đến máy khách và đưa vào bộ đệm ổ cắm trong hạt nhân. Vì vậy, chọn sẽ trả về dữ liệu đó có sẵn.
  • Ứng dụng khách chỉ đọc 100 byte trước tiên.
  • 900 byte khác sau đó sẽ được giữ trong bộ đệm ổ cắm hạt nhân. Cuộc gọi tiếp theo của lựa chọn một lần nữa sẽ trả về dữ liệu đó có sẵn, vì chọn xem xét bộ đệm trong bộ đệm hạt nhân.

Với SSL này là khác nhau:

  • Máy chủ gửi 1000 byte trong một ghi duy nhất. Sau đó, ngăn xếp SSL sẽ mã hóa 1000 byte này thành một khung SSL duy nhất và gửi khung này đến máy khách.
  • Khung SSL này sẽ được gửi tới máy khách và đưa vào bộ đệm ổ cắm hạt nhân. Vì vậy, chọn sẽ trả về dữ liệu đó có sẵn.
  • Hiện ứng dụng khách chỉ đọc 100 byte. Vì khung SSL chứa 1000 byte và vì nó cần khung đầy đủ để giải mã dữ liệu, ngăn xếp SSL sẽ đọc toàn bộ khung hình từ ổ cắm, không để lại gì trong bộ đệm ổ cắm hạt nhân. Sau đó nó sẽ giải mã khung và trả về 100 byte được yêu cầu cho ứng dụng. Phần còn lại của giải mã 900 byte sẽ được giữ trong ngăn xếp SSL trong không gian người dùng.
  • Vì bộ đệm trong bộ đệm hạt nhân trống nên cuộc gọi tiếp theo của lựa chọn sẽ không trả về dữ liệu đó. Vì vậy, nếu ứng dụng chỉ giao dịch với lựa chọn, nó sẽ không phải bây giờ mà vẫn còn dữ liệu chưa đọc, tức là 900 byte được lưu giữ trong ngăn xếp SSL.

Làm thế nào để đối phó với sự khác biệt này:

  • Một cách là luôn luôn cố gắng đọc ít nhất 32.768 dữ liệu, bởi vì đây là kích thước tối đa của một khung SSL. Bằng cách này, người ta có thể chắc chắn rằng không có dữ liệu nào vẫn được giữ trong ngăn xếp SSL (SSL đọc sẽ không đọc qua các ranh giới khung SSL).
  • Một cách khác là kiểm tra ngăn xếp SSL cho dữ liệu đã được giải mã trước khi gọi chọn. Chỉ khi không có dữ liệu được lưu giữ trong ngăn xếp SSL chọn nên được gọi. Để kiểm tra "dữ liệu đang chờ xử lý" như vậy, hãy sử dụng the pending method.
  • Cố gắng đọc thêm dữ liệu từ ổ cắm không chặn cho đến khi không có thêm dữ liệu. Bằng cách này, bạn có thể chắc chắn rằng không có dữ liệu nào vẫn đang chờ xử lý trong ngăn xếp SSL. Nhưng lưu ý rằng điều này cũng sẽ làm đọc trên socket TCP cơ bản và do đó có thể thích dữ liệu trên ổ cắm SSL so với dữ liệu trên ổ cắm khác (mà chỉ đọc sau khi chọn thành công). Đây là đề nghị bạn đã trích dẫn, nhưng tôi thích những người khác.
+0

Cảm ơn bạn @ steffen-ullrich, đây là câu trả lời rất rõ ràng. Rất tiếc, tôi đã thử sử dụng phương thức đang chờ xử lý trước khi sử dụng chọn và tôi luôn nhận được rằng có sẵn 0 byte. Tôi cũng đã thử nonblock_read (32768) và đó không phải là lừa. Sẽ thử một số chi tiết. – ostinelli

+0

Sẽ chấp nhận câu trả lời này vì nó làm rõ cách chính xác để giải quyết những vấn đề này. Cám ơn bạn một lần nữa! – ostinelli

Các vấn đề liên quan