0

我在 Ruby 3.0.2 中组装了一个 TCPServer,我发现我似乎无法在不阻塞的情况下读取整个数据包(直到套接字关闭)。

编辑:我试图做的事情有些混乱 - 我的错 - 所以只是为了帮助澄清:我想阅读到目前为止通过 TCP 连接发送的所有内容。(结束编辑)

我的第一次尝试是:

#!/snap/bin/ruby
require 'socket'

server = TCPServer.new('localhost', 4200)

loop {
  Thread.start(server.accept) do |connection|
    puts connection.gets  # The important line
  end
}

但这会挂起,直到客户端关闭连接。好的,所以我看一下connection.methods, 和 ruby​​ 文档,并尝试了一堆看起来很有希望的选项。基本上,有两种类型的读取方法:阻塞和非阻塞。

我尝试的阻塞方法是.read, .gets, .readlines, .readline, .recv, 和.recvmsg. 现在.read, .readlines, 和.gets所有挂起(直到套接字关闭) - 所以这没有帮助。其他的(例如.readlinerecv方法)不会阅读整个消息。现在,我可以读取每一行,直到看到一个空行并从那里解析 HTTP 标头。但是必须有更好的方法;我不想担心收到损坏的消息并挂起,因为我没有在标题末尾读取空行。

所以我去看了非阻塞选项。具体.recv_nonblock.recvmsg_nonblock。这两个抛出错误(资源暂时不可用 - recvfrom(2) 将阻塞资源暂时不可用 - recvmsg(2)分别)。

关于可能发生的事情的任何想法?我认为使用 Ruby 3 与我有关,因为在 Ruby 2.5 上尝试代码client.gets会返回一行(不会挂起),尽管.readlines会挂起 - 所以不确定发生了什么。

理想情况下,我可以调用类似的东西client.get_message,我会得到已发送的整个消息,但我也可以在 TCP 级别工作并获取数据包大小,读取该大小并重建消息从那里。

4

2 回答 2

0

感谢所有评论/回答的人,但我找到了我认为是 Socket 类的创建者想要的解决方案!

recv_nonblock方法采用一些可选参数 - 其中一个是 Socket 将存储它已读取内容的缓冲区。因此,像这样的调用client.recv_nonblock(1000, 0, buffer)将 Socket 中的最多 1000 个字符存储到buffer然后退出而不是阻塞。

为了让生活更轻松,我为 TCPSocket 类添加了一个猴子补丁:

class TCPSocket

  def eat_buffer
    contents = ''
    buffer = ''
    begin
    loop {
      recv_nonblock(256, 0, buffer)
      contents += buffer
    }
    rescue IO::EAGAINWaitReadable
      contents
    end
  end

end

Steffen 在评论中提出的观点得到了很好的理解——TCP 不是为这种方式设计的。这是一种 hacky(在坏的意义上)的方法,应该避免。

于 2021-09-14T02:03:14.817 回答
0

TCP 只是将您写入的字节传输到套接字,并保证按照它们发送的顺序接收。如果您有“消息”的概念,那么您需要将其添加到您的服务器和客户端中。

.gets特别是会阻塞,直到它读取一个新的“行”,或者您定义为字符串的分隔符的任何内容 - 请参阅文档IO#gets。这意味着在您的服务器从客户端接收到该字节之前,它将阻塞。

在您的客户端中,看看您是如何编写数据的 - 如果您使用的是 ruby​​,那么puts它会起作用,因为它会用新行终止字符串。如果您正在使用,write那么它只会写入没有新行的字符串

IE。

# client.rb
c = TCPSocket.new 'localhost', 5000
c.puts "foo"
c.write "bar"
c.write "baz\n"

# server.rb
s = TCPServer.new 5000
loop do
  client = s.accept
  puts client.gets
  puts client.gets
end

将输出

foo
barbaz
于 2021-09-13T05:29:45.273 回答