2016-04-22 5 views
6

erstellen Ich baue eine Client-Ruby-Bibliothek, die eine Verbindung zu einem Server herstellt und auf Daten wartet, aber auch Benutzer Daten durch Aufruf einer Methode senden können.Wie kann ich einen Zwei-Wege-SSL-Socket in Ruby

Der Mechanismus I verwenden, um eine Klasse zu haben, die ein Buchsenpaar initialisiert, etwa so:

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

Die Methode, die ich Entwicklern ermöglichen, rufen Daten sieht wie folgt aus, um den Server zu senden:

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

Dann habe ich eine Schleife in einem separaten Thread, wo ich aus einem socket wählen mit dem Server verbunden und von den @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 

Dies funktioniert wunderbar, aber nur mit einer normalen TCPSocket wie im Beispiel. Ich brauche ein OpenSSL::SSL::SSLSocket stattdessen verwenden, aber nach the IO.select docs:

Der beste Weg, IO.select zu verwenden ist es nach nonblocking Methoden wie read_nonblock Aufruf, write_nonblock usw.

[...]

Insbesondere die Kombination von nonblocking-Methoden und IO.select wird für IO wie Objekte wie OpenSSL :: SSL :: SSLSocket bevorzugt.

Nach diesem, ich brauche IO.selectnach nonblocking Methoden aufrufen, während in meiner Schleife ich es verwenden vor so kann ich aus zwei verschiedenen IO-Objekte auswählen.

Das gegebene Beispiel dafür, wie IO.select mit einer SSL-Socket zu verwenden ist:

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

Dies funktioniert jedoch nur, wenn IO.select mit einem einzigen IO-Objekt verwendet wird.

Meine Frage ist: Wie kann ich mein vorheriges Beispiel mit einem SSL-Socket arbeiten, vorausgesetzt, dass ich aus den @pipe_r und den socket Objekten auswählen muss?

EDIT: Ich habe versucht, was @ Steffen-Ullrich vorgeschlagen, jedoch ohne Erfolg. Ich konnte meine Tests bestanden mit dem folgenden machen:

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 

Dies sieht nicht so schlecht, aber jeder Eingang ist willkommen.

Antwort

2

Ich bin nicht vertraut mit Ruby selbst, aber mit den Problemen der Verwendung von Select mit SSL-Sockets. SSL-Sockets verhalten sich anders als TCP-Sockets, da SSL-Daten in Frames und nicht als Datenstrom übertragen werden, aber trotzdem Stream-Semantik auf die Socket-Schnittstelle angewendet wird.

Lassen Sie sich dies erklärt, mit einem Beispiel, zunächst eine einfache TCP-Verbindung:

  • Der Server sendet 1000 Bytes in einem einzigen Schreib.
  • Die Daten werden an den Client übermittelt und in den Kernel-Socket-Puffer übertragen. Somit wird ausgewählt, dass Daten verfügbar sind.
  • Die Clientanwendung liest zuerst nur 100 Byte.
  • Die anderen 900 Bytes werden dann im Kernel-Socket-Puffer gespeichert. Der nächste Aufruf von select wird erneut anzeigen, dass Daten verfügbar sind, da select den Socketpuffer im Kernel betrachtet.

Mit SSL ist das anders:

  • Der Server sendet 1000 Bytes in einem einzigen Schreib. Der SSL-Stack verschlüsselt diese 1000 Bytes dann in einen einzelnen SSL-Frame und sendet diesen Frame an den Client.
  • Dieser SSL-Frame wird an den Client geliefert und in den Kernel-Socket-Puffer eingefügt. Somit wird ausgewählt, dass Daten verfügbar sind.
  • Jetzt liest die Client-Anwendung nur 100 Bytes. Da der SSL-Frame 1000 Byte enthält und der vollständige Frame zum Entschlüsseln der Daten benötigt wird, liest der SSL-Stack den vollständigen Frame aus dem Socket und lässt nichts im Kernel-Socket-Puffer zurück. Es wird dann den Rahmen entschlüsseln und die angeforderten 100 Bytes an die Anwendung zurückgeben. Der Rest der entschlüsselten 900 Bytes wird im SSL-Stack im Benutzerbereich aufbewahrt.
  • Da der In-Kernel-Socket-Puffer leer ist, gibt der nächste Aufruf von Select die Daten nicht zurück. Wenn die Anwendung nur mit Auswählen befasst ist, wird es daher nicht, dass es noch ungelesene Daten gibt, d. H. Die 900 Bytes, die in dem SSL-Stapel gespeichert sind.

    • Eine Möglichkeit ist es, immer zu versuchen, mindestens 32.768 Daten zu lesen, denn dies ist die maximale Größe eines SSL-Rahmen ist:

    Wie dieser Unterschied zu beschäftigen. Auf diese Weise können Sie sicher sein, dass keine Daten im SSL-Stack gespeichert sind (SSL-Read wird nicht über SSL-Frame-Grenzen gelesen).

  • Eine andere Möglichkeit besteht darin, vor dem Aufruf von select den SSL-Stack auf bereits entschlüsselte Daten zu überprüfen. Nur wenn keine Daten im SSL-Stack gespeichert sind, sollte select aufgerufen werden. Um nach solchen "ausstehenden Daten" zu suchen, verwenden Sie the pending method.
  • Versuchen Sie, mehr Daten vom nicht blockierenden Socket zu lesen, bis keine Daten mehr verfügbar sind. Auf diese Weise können Sie sicher sein, dass im SSL-Stack noch keine Daten ausstehen. Beachten Sie jedoch, dass dies auch Lesevorgänge auf dem zugrunde liegenden TCP-Socket ausführt und daher Daten auf dem SSL-Socket im Vergleich zu Daten auf anderen Sockets bevorzugen (die nur nach erfolgreicher Auswahl gelesen werden). Dies ist die Empfehlung, die Sie zitiert haben, aber ich würde die anderen bevorzugen.
+0

Vielen Dank @ Steffen-Ullrich, das ist eine sehr klare Antwort. Leider habe ich versucht, die ausstehende Methode zu verwenden, bevor ich select benutze, und ich bekomme immer, dass 0 Bytes verfügbar sind. Ich habe auch nonblock_read (32768) versucht und das tut auch nicht den Trick. Werde mehr versuchen. – ostinelli

+0

Wird diese Antwort akzeptieren, da sie den richtigen Weg zur Lösung dieser Probleme verdeutlicht. Danke nochmal! – ostinelli

Verwandte Themen