Подтвердить что ты не робот

Ruby readpartial и read_nonblock не выбрасывают EOFError

Я пытаюсь понять и воссоздать простейший предпроккерный сервер по линиям единорога, где на сервере запускается 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 и Content-Type текста /html.

Проблема, с которой я сталкиваюсь, заключается в том, что мой сервер работает так, как должен, когда я читаю входящие запросы (путем нажатия на ссылку " http://localhost:9799" ), используя gets вместо чего-то вроде read или read_partial или read_nonblock. Когда я использую неблокирующие чтения, он никогда не бросает 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 или read_nonblock или заменяю IO.select([@client]) на break.

Вот когда код работает и возвращает ответ. Причина, по которой я намереваюсь использовать read_nonblock, - это единорог, использующий эквивалент, используя библиотеку kgio, которая реализует чтение без проверки.


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". Я бы очень признателен за помощь в этом.

Спасибо.

4b9b3361

Ответ 1

Сначала я хочу поговорить о некоторых базовых знаниях, EOF означает конец файла, он как сигнал будет посылать вызывающему абоненту, когда больше данных не может быть прочитано из источника данных, например, открыть файл и после прочтения всего файла получит EOF или просто закроет поток io.

Затем между этими 4 способами существует несколько различий

  • gets читает строку из потока, в ruby ​​использует $/ как разделитель строк по умолчанию, но вы можете передать параметр как разделитель строк, потому что если клиент и сервер не являются той же операционной системой, разделитель строк может отличаться, это метод block, если он никогда не встречает разделителя строк или EOF, он будет блокировать и возвращает nil, когда получает EOF, поэтому gets никогда не встретит EOFError.

  • read(length) читает длину байтов из потока, это метод block, если длина опущена, то он будет блокироваться до тех пор, пока не будет прочитано EOF, если есть длина, то она возвращается только один раз читать определенный объем данных или встречаться с EOF и возвращает пустую строку при получении EOF, поэтому read никогда не встретит EOFError.

  • readpartial(maxlen) читает максимум из maxlen байтов из потока, он будет читать доступные данные и немедленно возвращаться, он вроде бы похож на нетерпеливую версию read, если данные слишком велики, вы можете использовать readpartial вместо read, чтобы предотвратить блокировку, но он по-прежнему является блочным методом, он блокирует, если данные не доступны сразу, readpartial вызывает EOFError, если получает EOF > .

  • read_nonblock(maxlen) является добрым как readpartial, но, как и название, это метод неблокировать, даже нет доступных данных, он поднимет Errno::EAGAIN, это означает, что сейчас нет данных, вы должны заботиться об этой ошибке, обычно в Errno::EAGAIN предложение rescue должно сначала вызвать IO.select([conn]) для менее ненужного цикла, оно будет блокироваться до тех пор, пока соединение становится доступным для чтения, тогда retry, read_nonblock будет повышаться a EOFError, если получает EOF.

Теперь давайте посмотрим на ваш пример, так как я вижу, что вы делаете, - сначала попытайтесь прочитать данные, "набрав URL", это просто HTTP-запрос GET, некоторый текст, например "GET/HTTP/1.1\r\n", соединение сохраняется в HTTP/1.1 по умолчанию, поэтому использование readpartial или read_nonblock никогда не получит EOF, если не поставить заголовок Connection: close в ваш запрос, или изменить метод получения, как показано ниже:

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

Здесь вы не можете использовать read, потому что вы не знаете точную длину пакета запросов, используйте большую длину или просто пропущен вызовет блок.