5

そこで、大学の授業で、ソケットと 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)

4

1 に答える 1