2012-11-20 9 views
12

Ich versuche zu verstehen und neu erstellen einen einfachen Preforking-Server nach dem Vorbild der Einhorn, wo der Server auf Startgabeln 4 verarbeitet, die alle auf dem steuernden Socket warten (zu akzeptieren).Ruby readpartial und read_nonblock nicht werfen EOFError

Der steuernde Socket @control_socket bindet an 9799 und erzeugt 4 Arbeiter, die darauf warten, eine Verbindung zu akzeptieren. Die Arbeit an jedem Arbeiter getan ist wie folgt

 

     def spawn_child 
      fork do 
       $STDOUT.puts "Forking child #{Process.pid}" 
       loop do 
        @client = @control_socket.accept           
        loop do      
         request = gets    

         if request       
          respond(@inner_app.call(request))       
         else 
          $STDOUT.puts("No Request") 
          @client.close       
         end 
        end 
       end 
      end 
     end 

ich einen sehr einfachen Rack-App verwendet habe, die einfach einen String mit dem Statuscode 200 zurück und einem Content-Type text/html.

Das Problem, das ich Gesicht ist, dass mein Server funktioniert wie es soll, wenn ich eingehende Anfragen lesen (durch die URL auf „http://localhost:9799“ Schlagen) ein gets anstatt etwas wie read oder read_partial oder read_nonblock verwenden. Wenn ich nicht blockierende Lesevorgänge benutze, scheint es nie den EOFError zu werfen, der meiner Meinung nach bedeutet, dass er den EOF Zustand nicht empfängt.

Dies führt dazu, dass der Lesevorgang loop nicht abgeschlossen wird. Hier ist das Code-Snippet, das diese Arbeit macht.

 

     # Reads a file using IO.read_nonblock 
     # Returns end of file when using get but doesn't seem to return 
     # while using read_nonblock or readpartial 
       # The fact that the method is named gets is just bad naming, please ignore 
     def gets 
      buffer = ""   
      i =0 
      loop do 
       puts "loop #{i}" 
       i += 1 
       begin 
        buffer << @client.read_nonblock(READ_CHUNK) 
        puts "buffer is #{buffer}" 
       rescue Errno::EAGAIN => e 
        puts "#{e.message}" 
        puts "#{e.backtrace}" 
        IO.select([@client]) 
             retry 
       rescue EOFError 
        $STDOUT.puts "-" * 50 
        puts "request data is #{buffer}"  
        $STDOUT.puts "-" * 50 
        break   
       end 
      end 
      puts "returning buffer" 
      buffer 
     end 

 

jedoch der Code funktioniert perfekt, wenn die ich eine einfache gets statt read verwenden oder read_nonblock oder wenn die IO.select([@client]) mit einem break ersetzen.

Hier ist, wenn der Code funktioniert und die Antwort zurückgibt. Der Grund, warum ich read_nonblock verwenden möchte, ist Unicorn, das ein Äquivalent verwendet, das die kgio-Bibliothek verwendet, die einen nicht blockierenden Lesevorgang implementiert.

 

def gets 
    @client.gets 
end 
 

Der gesamte Code wird als nächstes eingefügt.

 

require 'socket' 
require 'builder' 
require 'rack' 
require 'pry' 

module Server 
    class Prefork 
     # line break 
     CRLF = "\r\n" 
     # number of workers process to fork 
     CONCURRENCY = 4 
     # size of each non_blocking read 
     READ_CHUNK = 1024 

     $STDOUT = STDOUT 
     $STDOUT.sync 

     # creates a control socket which listens to port 9799 
     def initialize(port = 21) 
      @control_socket = TCPServer.new(9799) 
      puts "Starting server..." 
      trap(:INT) { 
       exit 
      } 
     end 

     # Reads a file using IO.read_nonblock 
     # Returns end of file when using get but doesn't seem to return 
     # while using read_nonblock or readpartial 
     def gets 
      buffer = ""   
      i =0 
      loop do 
       puts "loop #{i}" 
       i += 1 
       begin 
        buffer << @client.read_nonblock(READ_CHUNK) 
        puts "buffer is #{buffer}" 
       rescue Errno::EAGAIN => e 
        puts "#{e.message}" 
        puts "#{e.backtrace}" 
        IO.select([@client]) 
             retry 
       rescue EOFError 
        $STDOUT.puts "-" * 50 
        puts "request data is #{buffer}"  
        $STDOUT.puts "-" * 50 
        break   
       end 
      end 
      puts "returning buffer" 
      buffer 
     end 

     # responds with the data and closes the connection 
     def respond(data) 
      puts "request 2 Data is #{data.inspect}" 
      status, headers, body = data 
      puts "message is #{body}" 
      buffer = "HTTP/1.1 #{status}\r\n" \ 
        "Date: #{Time.now.utc}\r\n" \ 
        "Status: #{status}\r\n" \ 
        "Connection: close\r\n"    
      headers.each {|key, value| buffer << "#{key}: #{value}\r\n"}   
      @client.write(buffer << CRLF) 
      body.each {|chunk| @client.write(chunk)}    
     ensure 
      $STDOUT.puts "*" * 50 
      $STDOUT.puts "Closing..." 
      @client.respond_to?(:close) and @client.close 
     end 

     # The main method which triggers the creation of workers processes 
     # The workers processes all wait to accept the socket on the same 
     # control socket allowing the kernel to do the load balancing. 
     # 
     # Working with a dummy rack app which returns a simple text message 
     # hence the config.ru file read. 
     def run   
      # copied from unicorn-4.2.1 
      # refer unicorn.rb and lib/unicorn/http_server.rb   
      raw_data = File.read("config.ru")   
      app = "::Rack::Builder.new {\n#{raw_data}\n}.to_app" 
      @inner_app = eval(app, TOPLEVEL_BINDING) 
      child_pids = [] 
      CONCURRENCY.times do 
       child_pids << spawn_child 
      end 

      trap(:INT) { 
       child_pids.each do |cpid| 
        begin 
         Process.kill(:INT, cpid) 
        rescue Errno::ESRCH 
        end 
       end 

       exit 
      } 

      loop do 
       pid = Process.wait 
       puts "Process quit unexpectedly #{pid}" 
       child_pids.delete(pid) 
       child_pids << spawn_child 
      end 
     end 

     # This is where the real work is done. 
     def spawn_child 
      fork do 
       $STDOUT.puts "Forking child #{Process.pid}" 
       loop do 
        @client = @control_socket.accept           
        loop do      
         request = gets    

         if request       
          respond(@inner_app.call(request))       
         else 
          $STDOUT.puts("No Request") 
          @client.close       
         end 
        end 
       end 
      end 
     end 
    end 
end 

p = Server::Prefork.new(9799) 
p.run 

Könnte jemand mir erklären, warum die mit ‚read_partial‘ liest versagen oder ‚read_nonblock‘ oder ‚lesen‘. Ich würde wirklich etwas Hilfe bei diesem Thema schätzen.

Danke.

+1

Das Verhalten, das Sie beschreiben, ist das Gegenteil von dem, was die Dokumente 'EOFError',' read_nonblock' usw. sagen. 'get' sollte' nil' zurückgeben, 'read_nonblock' sollte' EOFError' auslösen. –

+0

Was passiert, wenn Sie nur einen einzelnen Mitarbeiter starten? Es ist seltsam für mich, dass Sie eine Instanzvariable '' '' 'client'''' in der' '' spawn_child'''' Methode zuweisen. Würde nicht jeder Arbeiter diese Variable überschreiben? Oder stellt fork seinen eigenen Kontext her? – GSP

Antwort

9

Zuerst möchte ich über einige Grundkenntnisse sprechen, EOF bedeutet Ende der Datei, es ist wie Signal wird an den Aufrufer senden, wenn keine Daten mehr aus der Datenquelle gelesen werden können, zum Beispiel eine Datei öffnen und nach dem Lesen der gesamten Die Datei erhält einen EOF oder einfach den io Stream.

Dann gibt es einige Unterschiede zwischen diesen vier Methoden

  • gets eine Linie von Strom liest, in rubin es $/ als Standard-Zeilentrennzeichen verwendet, aber Sie können einen Parameter als Zeilentrennzeichen übergeben, denn wenn der Client und der Server sind nicht das gleiche Betriebssystem, die Linie Trennzeichen möglicherweise anders, es ist eine Block Methode, wenn nie treffen ein Trennzeichen oder EOF es wird blockiert, und gibt Null, wenn ein EOF erhält, so gets wird nie treffen ein EOFError.

  • read(length) liest Länge Bytes aus dem Strom, es ist eine Block Methode, wenn die Länge weggelassen wird, dann wird es bis EOF lesen blockieren, wenn es eine Länge ist dann gibt es nur einmal gelesen hat, bestimmte Datenmenge oder EOF erfüllen und gibt einen leeren String zurück, wenn ein EOF empfangen wird, so dass read niemals einen EOFError erfüllt.

  • readpartial(maxlen) liest höchstens maxlen Bytes aus dem Strom, wird es verfügbare Daten lesen und sofort zurückkehren, es ist irgendwie wie eine eifrige Version von read, wenn die Daten zu groß ist, dass Sie readpartial statt read um zu verhindern, blockieren können, aber es ist immer noch eine Block Block blockiert, wenn keine Daten sofort verfügbar sind, readpartial wird eine EOFError erhöht, wenn ein EOF empfängt.

  • read_nonblock(maxlen) ist Art wie readpartial, aber wie der Name schon sagt, es ist ein nonblock Methode, auch keine Daten verfügbar sie heben eine Errno::EAGAIN sofort bedeutet es jetzt keine Daten, sollten Sie diesen Fehler kümmern, in der Regel in Errno::EAGAIN Rettung Klausel sollte IO.select([conn]) zuerst für weniger unnötigen Zyklus aufrufen, wird es blockieren, bis der Conn zum Lesen verfügbar ist, dann retry, read_nonblock wird eine EOFError löst, wenn ein EOF empfängt.

Nun wollen wir Ihrem Beispiel sehen, wie ich sehe, was Sie tun, ist zu versuchen, Daten zu lesen durch „Schlagen Sie die URL“ zuerst, es ist nur eine HTTP-GET-Anforderung, einen Text wie „GET/HTTP/1.1 \ r \ n“, Verbindung sind am leben halten in HTTP/1.1 standardmäßig so readpartial oder read_nonblock verwendet, wird nie ein EOF erhalten, es sei denn, setzen Connection: close Header in Ihrer Anfrage, oder ändern Sie Ihre bekommt Methode wie folgt:

buffer = "" 
if m = @client.gets 
    buffer << m 
    break if m.strip == "" 
else 
    break 
end 
buffer 

Sie können read hier nicht verwenden, weil Sie die genaue Länge des Anfragepakets nicht kennen, verwenden Sie große Länge oder einfach einfach weggelassen Block.

Verwandte Themen