12

私はユニコーンのラインに沿って単純なプレフォークサーバーを理解し、再作成しようとしています。このサーバーでは、開始時にサーバーが 4 つのプロセスをフォークし、すべてが制御ソケットで (受け入れるために) 待機します。

制御ソケット@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 の文字列と text/html の Content-Type を返す、非常に単純なラック アプリを使用しました。

私が直面している問題は、サーバーが着信要求を読み取るときに (「http://localhost:9799 」の URL をヒットすることによって)またはgetsのようなものの代わりにa を使用して正常に動作することです。非ブロッキング読み取りを使用すると、EOFError がスローされることはないようです。これは、私の理解によれば、状態を受信しないことを意味します。readread_partialread_nonblockEOF

これにより、読み取り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の代わりに単純なものを使用する場合、またはをに置き換える場合、コードは完全に機能します。readread_nonblockIO.select([@client])break

これは、コードが機能して応答を返す場合です。read_nonblock を使用する理由は、unicorn がnon_blocking 読み取りを実装するkggioライブラリを使用して同等のものを使用するためです。

def gets
  @client.gets
end

次にコード全体を貼り付けます。

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」で読み取りが失敗する理由を誰かが説明してくれませんか。これについて何か助けていただければ幸いです。

ありがとう。

4

1 に答える 1

14

最初にいくつかの基本的な知識についてお話したいと思います。EOF はファイルの終わりを意味します。これは、データ ソースから読み取れるデータがなくなったときに信号が発信者に送信されるようなものです。たとえば、ファイルを開き、読み取り後にファイル全体が受信されます。 EOF、または単純に io ストリームを閉じます。

次に、これら4つの方法にはいくつかの違いがあります

  • getsストリームから行を読み取り、ruby では$/デフォルトの行区切り文字として使用しますが、パラメータを行区切り文字として渡すことができます。これは、クライアントとサーバーが同じオペレーティング システムでない場合、行区切り文字が異なる可能性があるためです。これはブロックメソッドです。行区切り文字または EOF に決して遭遇しない場合はブロックされ、EOF を受け取ると nil が返さgetsれるため、EOFError .

  • read(length)ストリームから length バイトを読み取ります。これはブロックメソッドです。長さが省略されている場合は、EOF が読み取られるまでブロックされます。長さが存在する場合は、一定量のデータを読み取ったか、EOF に達した場合に 1 回だけ戻ります。 EOF であるため、. に遭遇readすることはありませんEOFError

  • readpartial(maxlen)ストリームから最大 maxlen バイトを読み取ります。利用可能なデータを読み取り、すぐに戻りますread。データが大きすぎる場合は、ブロッキングを防止するreadpartial代わりに使用できますがread、それでもブロックメソッドであり、ブロックします。すぐに利用できるデータがない場合は、 EOF を受け取った場合readpartialに発生しEOFErrorます。

  • read_nonblock(maxlen)のようなものreadpartialですが、名前が言ったように、それは非ブロックメソッドであり、利用可能なデータがなくても、Errno::EAGAINすぐに発生します。これは、現在データがないことを意味します。通常、Errno::EAGAINレスキュー節ではIO.select([conn])、不要なサイクルを減らすために最初に呼び出す必要があります。 conn が読み取り可能になるまでブロックし、その後retryEOF を受信しread_nonblockた場合に発生しEOFErrorます。

あなたの例を見てみましょう。あなたがやっていることは、最初に「URLを打つ」ことでデータを読み込もうとすることです.デフォルトでは HTTP/1.1 でキープアライブするため、リクエストにヘッダーを入れるか、次のように gets メソッドを変更しない限り、 readpartialorread_nonblockを使用すると EOF を受け取ることはありません。Connection: close

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

readリクエストパッケージの正確な長さがわからないため、ここでは使用できません。大きな長さを使用するか、単純に省略するとブロックが発生します。

于 2012-12-27T15:07:43.520 に答える