Ruby readpartial и read_nonblock не выдают EOFError

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

Проблема, с которой я сталкиваюсь, заключается в том, что мой сервер работает, как и должен, когда я читаю входящие запросы (нажимая URL-адрес в " 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

Весь код вставляется дальше.

требуют 'сокета' требуют 'сборщика' требуют 'стойки' требуют 'модуля'pry'Класс сервера Prefork
        # разрыв строки CRLF  = "\r\n"
        # число рабочих процессов для разветвления CONCURRENCY = 4
        # размер каждого неблокирующего чтения READ_CHUNK = 1024

        $STDOUT = STDOUT
        $STDOUT.synC# создает управляющий сокет, который прослушивает порт 9799
        def initialize(порт = 21)
            @control_socket = TCPServer.new(9799) ставит ловушку "Запуск сервера..." (:INT) {
                exit
            }
        end

        # Считывает файл с помощью IO.read_nonblock
        # Возвращает конец файла при использовании get, но не возвращает # при использовании read_nonblock или readpartial def получает buffer = "" цикл i =0 do помещает "loop #{i}"
                i += 1 начало буфера << @client.read_nonblock(READ_CHUNK) помещает "буфер является # {буфер}" спасения Errno::EAGAIN => e помещает "#{e.message}" ставит "#{e.backtrace}"
                    IO.select([@client]) повторить попытку восстановления EOFError
                    $STDOUT.puts "-" * 50 выводит "данные запроса: #{буфер}" $ STDOUT.puts "-" * * 50 end end end ставит "буфер возврата" конец буфера # отвечает данными и закрывает соединение def response (data) помещает "request 2 Data is # {data.inspect}" статус, заголовки, body = data помещает "message is # {body}" buffer = "HTTP / 1.1 # {status}\r\n" \
                     "Date: #{Time.now.utc} \ r \ n "\" Статус: #{status} \ r \ n "\" Соединение: закрыть \ r \ n "headers.each {| ключ, значение | буфер << "# {ключ}: #{значение}\r\n"}          
            @client.write(буфер << CRLF)
            body.each {|chunk| @client.write(chunk)} обеспечивает $STDOUT.puts "*" * 50
            $STDOUT.puts "Closing..."
            @client.respond_to?(:close) и @client.close
        end

        # Основной метод, который вызывает создание рабочих процессов # Все рабочие процессы ждут, чтобы принять сокет на том же # управляющем сокете, позволяя ядру выполнять балансировку нагрузки.
        # Работа с фиктивным стоечным приложением, которое возвращает простое текстовое сообщение #, следовательно, файл config.ru прочитан. def run # скопировано из unicorn-4.2.1
            # см. unicorn.rb и 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

                end exit} цикл do
                pid = Process.wait помещает "неожиданно завершился процесс #{pid}"
                child_pids.delete(pid)
                child_pids << spawn_child
            end
        end

        # This где настоящая работа сделана.
        def spawn_child
            fork do
                $STDOUT.puts цикл "Forking child #{Process.pid}" do do цикл @client = @control_socket.accept do                     
                        request = получает ответ на запрос (@inner_app.call(request)) иначе $STDOUT.puts("Нет запроса")
                            @client.close конец конец конец конец конец конец конец p = Server::Prefork.new(9799)
p.run 

Может ли кто-нибудь объяснить мне, почему чтения не удается с "read_partial" или "read_nonblock" или "read". Я был бы очень признателен за помощь в этом.

Благодарю.

2 ответа

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

Тогда есть несколько различий между этими 4 методами

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

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

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

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

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

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

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

      r, stop = "", false
io = IO.new(2)
EXIT_SYMBOL = 'q'

until stop 
  begin
    r = io.read_nonblock(256)
  rescue IO::WaitReadable
    retry unless r.scan(EXIT_SYMBOL).first
    
    r, stop  = "", true
  end
end

Для выхода нужно ввести символ 'q' и нажать Enter

Другие вопросы по тегам