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