2016-04-22 49 views
6

Buduję bibliotekę Ruby klienta, która łączy się z serwerem i czeka na dane, ale także pozwala użytkownikom wysyłać dane przez wywołanie metody.Jak utworzyć dwukierunkowe gniazdo SSL w Ruby

Mechanizm używam jest mieć klasę, która inicjuje parę gniazd, tak:

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

metoda, która mi pozwoli deweloperom zadzwonić do wysyłania danych na serwer wygląda następująco:

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

Wtedy muszę pętlę w oddzielnym wątku, gdzie wybrać z socket podłączonego do serwera, a z @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 

To działa pięknie, , ale tylko z normalnym TCPSocket, jak na przykładzie. Muszę użyć OpenSSL::SSL::SSLSocket zamiast jednak zgodnie the IO.select docs:

Najlepszym sposobem korzystania IO.select powołuje go po metodach nonblocking takich jak read_nonblock, write_nonblock itp

[...]

Szczególnie połączenie metod niezablokowania i IO.select jest preferowane dla obiektów typu IO takich jak OpenSSL :: SSL :: SSLSocket.

Zgodnie z tym, muszę zadzwonić IO.selectpo metod blokowania, podczas gdy w moim pętli Używam go przed więc mogę wybrać jedną z 2 różnych obiektów IO.

podanym przykładzie, w jaki sposób korzystać z IO.select z gniazdem SSL jest:

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

Jednak to działa tylko wtedy, gdy jest używany IO.select z pojedynczego obiektu IO.

Moje pytanie brzmi: w jaki sposób mogę sprawić, aby mój poprzedni przykład działał z gniazdem SSL, biorąc pod uwagę, że muszę wybrać zarówno z obiektów @pipe_r i socket?

EDYCJA: Próbowałem, co sugerował @ steffen-ullrich, jednak bez skutku. Udało mi się przetestować następujące parametry:

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 

To nie wygląda tak źle, ale wszelkie dane wejściowe są mile widziane.

Odpowiedz

2

Nie jestem zaznajomiony z samym ruby, ale z problemami z używaniem select z gniazdami SSL. Gniazda SSL zachowują się inaczej niż gniazda TCP, ponieważ dane SSL są przesyłane w ramkach, a nie jako strumień danych, ale mimo to semantyka strumieniowa jest stosowana do interfejsu gniazda.

Wyjaśnijmy to na przykładzie, najpierw za pomocą prostego połączenia TCP:

  • Serwer wysyła 1000 bajtów w jednym zapisie.
  • Dane zostaną dostarczone do klienta i umieszczone w buforze gniazda jądra. W ten sposób select zwróci te dane, które są dostępne.
  • Aplikacja kliencka wczytuje tylko 100 bajtów.
  • Pozostałe 900 bajtów będzie przechowywane w buforze jądra w jądrze. Następne wywołanie select ponownie zwróci te dane, ponieważ opcja select sprawdza bufor jądra w jądrze.

Z SSL jest inna:

  • Serwer wysyła 1000 bajtów w jednym zapisie. Stos SSL następnie zaszyfruje te 1000 bajtów w jedną ramkę SSL i wyśle ​​tę ramkę do klienta.
  • Ta ramka SSL zostanie dostarczona do klienta i umieszczona w buforze gniazda jądra. W ten sposób select zwróci te dane, które są dostępne.
  • Teraz aplikacja kliencka odczytuje tylko 100 bajtów. Ponieważ ramka SSL zawiera 1000 bajtów, a do odszyfrowania danych potrzebna jest pełna klatka, stos SSL odczyta pełną klatkę z gniazda, nie pozostawiając niczego w buforze gniazd jądra. Następnie odszyfruje ramkę i zwraca wymagane 100 bajtów do aplikacji. Pozostałe odszyfrowane 900 bajtów będzie przechowywane w stosie SSL w przestrzeni użytkownika.
  • Ponieważ pusty bufor gniazda jądra jest pusty, następne wywołanie select nie zwróci tych danych. Tak więc, jeśli aplikacja dotyczy tylko selekcji, nie będzie to oznaczać, że nadal istnieją nieprzeczytane dane, tj. 900 bajtów przechowywanych w stosie SSL.

Jak radzić sobie z tą różnicą:

  • Jednym ze sposobów jest zawsze staram się czytać co najmniej 32768 danych, ponieważ jest to maksymalna wielkość ramki SSL. W ten sposób można mieć pewność, że żadne dane nie są przechowywane w stosie SSL (odczyt SSL nie odczyta granic ramek SSL).
  • Innym sposobem jest sprawdzenie stosu SSL dla już odszyfrowanych danych przed wywołaniem wyboru. Tylko jeśli żadne dane nie są przechowywane w stosie SSL, należy wybrać select. Aby sprawdzić takie "oczekujące dane", użyj the pending method.
  • Spróbuj odczytać więcej danych z nieblokującego gniazda, dopóki nie będą dostępne żadne dane. W ten sposób możesz mieć pewność, że żadne dane nie są wciąż oczekujące w stosie SSL. Należy jednak pamiętać, że spowoduje to również odczyt na bazowym gnieździe TCP, a zatem może preferować dane na gnieździe SSL w porównaniu z danymi z innych gniazd (które są odczytywane tylko po pomyślnym wybraniu). To zalecenie, które zacytowałeś, ale wolę inne.
+0

Dziękuję @ steffen-ullrich, jest to bardzo jasna odpowiedź. Niestety próbowałem użyć oczekującej metody przed użyciem select i zawsze otrzymuję, że są dostępne 0 bajtów. Próbowałem też nonblock_read (32768) i to też nie działa. Spróbuję trochę więcej. – ostinelli

+0

Przyjmie tę odpowiedź, ponieważ wyjaśnia poprawny sposób rozwiązania tych problemów. Jeszcze raz dziękuję! – ostinelli