そこで、大学の授業で、ソケットと Ruby を使用して基本的な HTTP 永続接続をシミュレートしようとしています。
ポイントは、HTTP GET のように、ファイル パスを受け取り、ファイルの内容を返す、複数のクライアントを処理できるサーバーを構築することです。
現在のサーバー実装は、クライアントをリッスンしてループし、着信接続があると新しいスレッドを起動し、このソケットからファイル パスを読み取ります。それは非常にばかげていますが、接続ごとに 1 つの要求である、存在しない接続で作業する場合は問題なく動作します。
しかし、それらは永続的でなければなりません。
つまり、クライアントは接続を閉じることを心配する必要はありません。非永続的なバージョンでは、サーバーは応答をエコーして接続を閉じます -さようならクライアント、さようなら。しかし、永続的であるということは、サーバー スレッドがループして、さらに要求がなくなるまで待機する必要があることを意味します。サーバーはどのようにそれを知っていますか? そうではありません!ある種のタイムアウトが必要です。Ruby の Timeout でそれをやろうとしましたが、うまくいきませんでした。
いくつかの解決策をグーグルで調べます-タイムアウトモジュールの使用を避けるように徹底的にアドバイスされていることに加えて-スレッドやものを使用するよりも、このソケット待機の問題をうまく処理するはずの IO.select メソッドに関する多くの投稿を見てきました (これは本当にクールに聞こえますが、 Ruby スレッドがどのように機能するか (しないか) を考慮します)。ここで IO.select がどのように機能するかを理解しようとしていますが、現在のシナリオではまだ機能させることができませんでした。
だから私は基本的に2つのことを尋ねます:
スレッドベースのソリューション、低レベルのソケットオプション、または IO.select マジックを使用して、サーバー側でこのタイムアウトの問題を効率的に処理するにはどうすればよいですか?
クライアント側は、サーバーが接続の側を閉じたことをどのように知ることができますか?
サーバーの現在のコードは次のとおりです。
require 'date'
module Sockettp
class Server
def initialize(dir, port = Sockettp::DEFAULT_PORT)
@dir = dir
@port = port
end
def start
puts "Starting Sockettp server..."
puts "Serving #{@dir.yellow} on port #{@port.to_s.green}"
Socket.tcp_server_loop(@port) do |socket, client_addrinfo|
handle socket, client_addrinfo
end
end
private
def handle(socket, addrinfo)
Thread.new(socket) do |client|
log "New client connected"
begin
loop do
if client.eof?
puts "#{'-' * 100} end connection"
break
end
input = client.gets.chomp
body = content_for(input)
response = {}
if body
response.merge!({
status: 200,
body: body
})
else
response.merge!({
status: 404,
body: Sockettp::STATUSES[404]
})
end
log "#{addrinfo.ip_address} #{input} -- #{response[:status]} #{Sockettp::STATUSES[response[:status]]}".send(response[:status] == 200 ? :green : :red)
client.puts(response.to_json)
end
ensure
socket.close
end
end
end
def content_for(path)
path = File.join(@dir, path)
return File.read(path) if File.file?(path)
return Dir["#{path}/*"] if File.directory?(path)
end
def log(msg)
puts "#{Thread.current} -- #{DateTime.now.to_s} -- #{msg}"
end
end
end
アップデート
IO.select メソッドを使用してタイムアウトの動作をシミュレートすることはできましたが、新しい接続を受け入れるためのいくつかのスレッドとリクエストを処理するための別のスレッドを組み合わせると、実装がうまくいきません。並行性は状況を狂って不安定にします。この解決策を使用するより良い方法を見つけられない限り、私はおそらくそれに固執しません.
更新 2
これを処理するには Timeout が依然として最善の方法であるようです。より良い選択肢が見つかるまで、私はそれに固執しています。ゾンビ クライアント接続の処理方法がまだわかりません。
解決
私は IO.select を使用することになります (webrick コードを見てインスピレーションを得ました)。ここで最終バージョンを確認してください(lib/http/server/client_handler.rb)