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". Я бы очень признателен за помощь в этом.
Спасибо.
Ответы
Ответ 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
, потому что вы не знаете точную длину пакета запросов, используйте большую длину или просто пропущен вызовет блок.