10

学校の課題で、Ruby とソケット ライブラリを使用して単純な HTTP サーバーを作成しようとしています。

今のところ、単純な hello であらゆる接続に応答するようにできます。

require 'socket'

server = TCPServer.open 2000
puts "Listening on port 2000"

loop {
  client = server.accept()
  resp = "Hello?"
  headers = ["HTTP/1.1 200 OK",
             "Date: Tue, 14 Dec 2010 10:48:45 GMT",
             "Server: Ruby",
             "Content-Type: text/html; charset=iso-8859-1",
             "Content-Length: #{resp.length}\r\n\r\n"].join("\r\n")
  client.puts headers
  client.puts resp
  client.close
}

これは期待どおりに機能します。ただし、サーバーに接続したばかりの人を教えてもらうと

puts "Client: #{client.addr[2]}"

Chromium(ブラウザ)を使用してlocalhost:2000/(1回だけ)接続すると、次のようになります。

Client: 127.0.0.1
Client: 127.0.0.1
Client: 127.0.0.1
Client: 127.0.0.1

これは のような補助ファイルを要求している Chromiumfavicon.icoであり、私のスクリプトが奇妙なことをしているのではないので、着信要求を調査したいと思いました。resp = "Hello?"行を次のように置き換えました

resp = client.read()

そしてサーバーを再起動しました。Chromium でリクエストを再送信しましたが、すぐに返される代わりにハングしました。その間、Client: 127.0.0.1サーバー出力に出力がありました。Chromiumで「停止」ボタンを押すと、サーバーがクラッシュしました

server.rb:16:in `write': Broken pipe (Errno::EPIPE)
    from server.rb:16:in `puts'
    from server.rb:16:in `block in <main>'
    from server.rb:6:in `loop'
    from server.rb:6:in `<main>'

明らかに、期待される動作は着信要求を応答として送り返すものだったので、何か間違ったことをしています。

私は何が欠けていますか?

4

1 に答える 1

21

Chromeと4つの接続についてはよくわかりませんが、リクエストを正しく読み取る方法についての質問に答えようと思います。

まず第一にIO#read、この場合は機能しません。ドキュメントによると、readパラメータなしでEOFに遭遇するまで読み取りますが、そのようなことは何も起こりません。ソケットは無限のストリームであり、ソケットの「全体」メッセージがないため、メッセージ全体を読み取るためにそのメソッドを使用することはできません。のような整数でreadを使用することもできread(100)ますが、それとにかくある時点でブロックされます。

基本的に、ソケットの読み取りはファイルの読み取りとは大きく異なります。ソケットは非同期で更新され、読み取ろうとする時間とは完全に無関係です。10バイトを要求した場合、コードのこの時点では、5バイトしか使用できない可能性があります。IOをブロックすると、呼び出しread(10)ハングし、さらに5バイトが使用可能になるまで、または接続が閉じられるまで待機します。これは、10バイトのパケットを繰り返し読み取ろうとすると、ある時点でハングすることを意味します。ソケットを読み取る別の方法は、非ブロッキングIOを使用することですが、これはあなたの場合はそれほど重要ではなく、それ自体が長いトピックです。

したがって、ブロッキングIOを使用してデータにアクセスする方法の例を次に示します。

loop {
  client = server.accept

  while line = client.gets
    puts line.chomp
    break if line =~ /^\s*$/
  end

  # rest of loop ...
}

getsメソッドは、改行に遭遇するまでソケットからの読み取りを試みます。これHTTPリクエストのある時点で発生するため、メッセージ全体が1つずつ転送される場合でもgets、出力から1行を返す必要があります。それらが存在する場合、line.chomp呼び出しは最後の改行を切断します。読み取られた行が空の場合は、HTTPヘッダーが転送されており、ループを安全に中断できることを意味します(もちろん、これを条件に入れることができますwhile)。リクエストは、サーバーが起動されたコンソールにダンプされます。本当にブラウザに送り返したい場合は、考え方は同じです。行を別の方法で処理する必要があります。

loop {
  client = server.accept

  lines = []
  while line = client.gets and line !~ /^\s*$/
    lines << line.chomp
  end

  resp = lines.join("<br />")
  headers = ["http/1.1 200 ok",
            "date: tue, 14 dec 2010 10:48:45 gmt",
            "server: ruby",
            "content-type: text/html; charset=iso-8859-1",
            "content-length: #{resp.length}\r\n\r\n"].join("\r\n")
  client.puts headers          # send the time to the client
  client.puts resp
  client.close
}

read壊れたパイプについては、データにアクセスしようとしているときにブラウザが強制的に接続を切断するため、このエラーが発生します。

于 2011-09-24T17:22:20.657 に答える