在Ruby中阅读TCPSocket

kninwzqo  于 2023-05-17  发布在  Ruby
关注(0)|答案(1)|浏览(133)

此代码向www.example.com网站发送两个HTTP请求:

require 'socket'

@host = 'www.example.com'
@port = 80
@path = "/"

# Build HTTP request
def request(close=false)
  "GET #{@path} HTTP/1.1\r\nHost: #{@host}#{"\r\nConnection: close" if close}\r\n\r\n"
end

# Build socket
socket = TCPSocket.open(@host,@port)  # Connect to server

# Send request twice via socket
2.times {socket.print request}

以下是我发现的阅读响应的各种方法:

# Method 1: close_write and read
socket.close_write # Without this line, the next line hangs
response = socket.read
puts response.length

# Method 2: send another http request with 'Connection: close' header, then use 'read'
socket.print request(true) # Without this line, the next line hangs
response = socket.read
puts response.length

# Method 3: recv
# puts socket.eof?  # Method fails when I add this line
r1, r2 = socket.recv(1000000), socket.recv(1000000)
puts r1.length, r2.length

# Method 4: IO.select and read_nonblock
puts socket.eof?
# IO.select([socket])  # The code still works without this IO.select...
r1 = socket.read_nonblock(9999999)
IO.select([socket])  # ...but not without this one
r2 = socket.read_nonblock(9999999)
puts r1.length, r2.length
puts socket.eof? # Hangs for ages before returning 'true'

问题:

  1. socket.close_write行在方法1中到底做了什么,为什么这个方法必须工作?
    1.在方法2中,是“连接:close'头以某种方式实现了与方法1中的'socket.close_write'行相同的结果?如果不是,它在做什么,为什么方法的其余部分必须工作?
    1.为什么在方法3中,添加注解行'putsocket.eof?',导致代码的其余部分挂起?
    1.在方法3中,recv调用如何以及为什么在HTTP响应结束时停止(而不是同时拾取下一个响应)?
    1.为什么方法4中的第二个IO.select是必要的,而第一个不是?
    1.IO.select实际上是做什么的?
    1.为什么最后一行'放入socket.eof?',在方法4中挂起很长时间才返回true?
    1.有没有一种通用的方法来检查套接字当前期望的响应数量,并从套接字中阅读该数量的响应,而无需关闭套接字进行写入?
    最后,如果在这里的答案是不可能的,有没有一个好的资源,我可以得到一些清晰的所有上述内容,并从TCP套接字(或一般网络套接字)阅读一般清晰?
mu0hgdu0

mu0hgdu01#

socket.close_write不是必需的。我的意思是你可以通过socket.read得到socket返回的值,但是你需要等待一段时间。原因是因为您试图用socket.read读取整个流。这需要时间你可以通过执行以下操作来找出socket返回的内容:

socket.each_line do |line|
  puts line
end

顺便说一句,Nagle's algorithm也让它变慢了。
close_write所做的是让客户端半关闭套接字。当服务器端注意到这一点时,它也会关闭自己的端。这样你就可以很快读完阅读。
或者你可以使用IO::select。正如文档所说:
它监视给定的IO对象数组,等待一个或多个IO对象准备好阅读,准备好写入,并分别有挂起的异常,并返回一个包含这些IO对象数组的数组。如果给定了可选的超时值,并且在超时秒内没有IO对象就绪,则返回nil。

ready = IO.select([socket], nil, nil, 10)
if ready
  # do something
else
  # raise timeout
end

这里我们传递第一个参数,它是对象ready for reading,最后一个是你想要设置的超时。这意味着如果阅读在10秒内没有准备好,它将返回nil,然后引发超时错误。

相关问题