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/(仅一次),我得到:

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

我假设这是 Chromium 请求辅助文件,例如favicon.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 和四个连接,但我会尝试回答您有关如何正确阅读请求的问题。

首先,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 请求,这在某个时间点发生,因此即使整个消息是逐段传输的,gets也应该从输出中返回一行。如果它们存在,该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 回答