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