2012-11-20 129 views
12

我試圖理解和沿麒麟的線重新建立一個簡單的preforking所在服務器的控制插座上開始叉子4個處理所有的等待(接受)的服務器。紅寶石readpartial和read_nonblock不扔的EOFError

的控制插座@control_socket結合9799,併產生4名工人伺候接受連接。對每個工人完成的工作如下

 

     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 

我用一個很簡單的機架應用程序,它簡單地返回與狀態碼200和文本/ HTML的內容類型的字符串。

我所面臨的問題是,我的服務器使用gets,而不是像readread_partial或​​的作品,因爲它應該當我讀到傳入的請求(通過點擊在「http://localhost:9799」的URL)。當我使用非阻塞讀它似乎永遠不會丟的EOFError,根據我的理解,這意味着它不會收到EOF狀態。

這會導致讀取loop不完整的。這是執行這項工作的代碼片段。

 

     # 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 

 

然而代碼完美地工作,如果我用一個簡單gets而不是read或​​或者用break更換IO.select([@client])

下面是當代碼工作並返回響應。我打算用read_nonblock的原因是麒麟使用kgio庫,實現讀取non_blocking使用等效。

 

def gets 
    @client.gets 
end 
 

整個碼被下粘貼。

 

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 

有人可以向我解釋爲什麼read_partial,read_nonblock或read的讀取失敗。我非常感謝這方面的幫助。

謝謝。

+1

您描述的行爲與文檔「EOFError」,「read_nonblock」等的含義相反。 'get'應該返回'nil','read_nonblock'應該引發'EOFError'。 –

+0

如果您只啓動一名工人,會發生什麼情況?對於我來說,在''''''spawn_child'''方法中分配一個實例變量''@ client'''是很奇怪的。每個工人都不會重寫那個變量嗎?或者,叉是否建立它自己的上下文? – GSP

回答

9

首先我想談談一些基本的知識,EOF意思是文件結束,它就像信號發送給調用者,當沒有更多的數據可以從數據源讀取時,例如打開一個文件並在讀完整個文件將收到一個EOF,或者只是簡單地關閉io流。

再就是這4種方法

  • gets讀取流線之間的一些差異,在紅寶石它使用$/作爲默認的行分隔符,但你可以將參數作爲行分隔符,因爲如果客戶端和服務器不是相同的操作系統,行分隔符可能不同,這是一個方法,如果永遠不會遇到行分隔符或EOF它會阻塞,並返回nil時收到一個EOF,所以gets永遠不會遇見一個EOFError

  • read(length)從流讀出長度的字節,這是一個方法中,如果省略長度那麼它會被阻塞,直到EOF讀,如果有一個長度則返回數據的僅一次具有讀取一定量或滿足EOF ,並在收到EOF時返回空字符串,因此read將永遠不會遇到EOFError

  • readpartial(maxlen)讀最多的maxlen個字節流,它會讀取可用的數據並立即返回,這有點像read一個渴望版本,如果數據太大,你可以使用readpartial而不是read堵塞預防,但它仍然是一個方法,它會阻止如果沒有立即可用數據,readpartial將提高EOFError如果收到EOF

  • read_nonblock(maxlen)是一種像readpartial,但喜歡這個名字說,這是一個非塊方法,即使沒有新的數據也引發Errno::EAGAIN立即就意味着沒有數據,現在,你應該在Errno::EAGAIN救援關心這個錯誤,通常子句應該首先調用IO.select([conn])來減少不必要的循環,它將阻塞,直到conn變爲可用,然後retry,​​將提高EOFError如果收到EOF

現在讓我們看看你的榜樣,因爲我看到你在做什麼是試圖通過「打的網址」先讀取數據,它只是一個HTTP GET請求,一些文字,如「GET/HTTP/1.1 \ r \ n」個,連接在HTTP永葆/ 1.1在默認情況下,因此,使用readpartial或​​將永遠不會收到一個EOF,除非把Connection: close頭在你的請求,或者改變您獲得方法如下:

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

你不能在這裏使用read,因爲你不知道請求包的確切長度,使用大的長度或者只是簡單的省略會導致塊。