2012-11-20 10 views
12

Próbuję zrozumieć i odtworzyć prosty serwer prefabrykacji wzdłuż linii jednorożca, gdzie serwer na początku widnieje 4 procesy, które wszystkie czekają (aby zaakceptować) na kontrolnym gnieździe.Ruby readpartial i read_nonblock nie rzucają EOFError

Gniazdo sterujące @control_socket łączy się z 9799 i spawnuje 4 pracowników oczekujących na połączenie. Praca odbywa się na każdego pracownika jest następujący

 

     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 

Użyłem bardzo prostą aplikację w szafie, które po prostu zwraca ciąg znaków z kodem statusu 200 i Content-Type of text/html.

Problemem jest to, że mogę zmierzyć mój serwer działa tak jak powinien, kiedy czytam przychodzące żądania (trafiając url w „http://localhost:9799”) za pomocą gets zamiast coś read lub read_partial lub read_nonblock. Kiedy używam nie blokujących odczytów, nigdy nie wydaje mi się, że EOFError, który zgodnie z moim rozumowaniem oznacza, że ​​nie otrzymuje stanu EOF.

To powoduje, że przeczytany loop nie jest kompletny. Oto fragment kodu, który wykonuje tę pracę.

 

     # 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 

 

Jednak kod działa doskonale, jeśli mogę użyć prostego gets zamiast read lub read_nonblock czy wymienić IO.select([@client]) z break.

Oto, kiedy kod działa i zwraca odpowiedź. Powodem, dla którego zamierzam użyć read_nonblock jest jednorożec używa odpowiednika przy użyciu biblioteki kgio, która implementuje odczyt bez blokowania.

 

def gets 
    @client.gets 
end 
 

Cały kod zostanie wklejony dalej.

 

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 

Może ktoś mi wytłumaczyć, dlaczego nie czyta z „read_partial” lub „read_nonblock” lub „przeczytane”. Byłbym wdzięczny za pomoc w tej sprawie.

Dzięki.

+1

Zachowanie, które opisujesz, jest przeciwieństwem tego, co mówią dokumenty "EOFError", "read_nonblock" itp. 'get' powinno zwracać' nil', 'read_nonblock' powinno wywoływać' EOFError'. –

+0

Co się dzieje, jeśli uruchamiasz tylko jednego pracownika? To dla mnie dziwne, że przypisujesz zmienną instancji '' '@ client'''' w metodzie' 'spawn_child'''''. Czy każdy pracownik nie zastąpi tej zmiennej? Lub czy widelec ustanawia swój własny kontekst? – GSP

Odpowiedz

9

Najpierw chcę porozmawiać o podstawowej wiedzy, EOF oznacza koniec pliku, to tak, jakby sygnał był wysyłany do osoby dzwoniącej, gdy nie ma więcej danych do odczytania ze źródła danych, na przykład, otwórz plik i po przeczytaniu całego plik otrzyma EOF lub po prostu zamknie strumień io.

Wtedy istnieje kilka różnic tych 4 metod

  • gets Czyta wiersz z potoku, rubinu wykorzystuje $/ jako ogranicznik domyślna linii, ale można przekazać parametr jako linia separatora, ponieważ jeśli klient i serwer nie są tym samym systemem operacyjnym, ogranicznik linii może być inny, to jest metoda blok, jeśli nigdy nie napotkasz ogranicznika linii lub EOF, który będzie blokować, i zwróci zero, gdy otrzyma EOF, więc gets nigdy się nie spotka EOFError.

  • read(length) odczytuje długość bajtów ze strumienia, to blok metoda, jeśli długość jest pominięta następnie będzie blokować aż czytać EOF, jeśli jest długość to zwraca tylko raz przeczytał pewną ilość danych lub spotkać EOF i zwraca pusty łańcuch po otrzymaniu EOF, więcread nigdy nie spotka się z EOFError.

  • readpartial(maxlen) czyta co najwyżej maxlen bajtów ze strumienia, odczyta dostępne dane i powrócić natychmiast, to niby jak chętny wersji read, jeśli dane są zbyt duże, można użyć readpartial zamiast read aby zapobiec blokowaniu, ale nadal jest to metoda blokująca, jeśli żadne dane nie są dostępne od razu, readpartial spowoduje podniesienie EOFError, jeśli otrzyma EOF.

  • read_nonblock(maxlen) jest trochę jak readpartial, ale jak sama nazwa mówi jest to NONBLOCK metoda, nawet brak dostępnych danych to podnieść Errno::EAGAIN razu oznacza to żadnych danych w tej chwili, trzeba dbać o tym błędzie, zwykle w Errno::EAGAIN ratunek klauzula powinna najpierw zadzwonić pod numer IO.select([conn]), aby uzyskać mniej niepotrzebny cykl, zablokuje się, dopóki nie stanie się dostępna do odczytania, a następnie retry, read_nonblock spowoduje podniesienie EOFError, jeśli otrzyma EOF.

Teraz zobaczmy swój przykład, jak widzę, co robisz jest próbować odczytać dane przez „uderzanie url” Po pierwsze, to tylko żądania HTTP GET, jakiś tekst jak „GET/HTTP/1.1 \ r \ n”, połączenie to utrzymać przy życiu w HTTP/1.1 domyślnie, więc korzystanie readpartial lub read_nonblock nigdy nie otrzyma EOF, chyba umieścić Connection: close nagłówek w swoim wniosku, stonie dostaje sposób jak poniżej:

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

Nie możesz tutaj użyć read, ponieważ nie znasz dokładnej długości pakietu żądania, nie użyjesz dużej długości lub po prostu pominięto blok.