【发布时间】:2021-11-08 09:41:41
【问题描述】:
我在 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 都挂起(直到套接字关闭)——所以这没有帮助。其他的(例如.readline、recv 方法)不会阅读整个消息。现在,我可以读取每一行,直到看到一个空行并从那里解析 HTTP 标头。但是必须有更好的方法;我不想担心收到损坏的消息并挂起,因为我没有读取标题末尾的空行。
所以我去看了非阻塞选项。特别是.recv_nonblock 和.recvmsg_nonblock。这两个抛出错误(资源暂时不可用 - recvfrom(2) 会阻塞 和 资源暂时不可用 - recvmsg(2) 分别)。
对可能发生的事情有任何想法吗?我认为它与我使用 Ruby 3 有关系,因为在 Ruby 2.5 上尝试代码,client.gets 返回一行(不会挂起),尽管 .readlines 确实挂起 - 所以不会确定发生了什么。
理想情况下,我可以像 client.get_message 那样调用一些东西,我会得到已发送的整个消息,但我也可以在 TCP 级别工作并获取数据包大小,阅读大小,并从那里重建消息。
【问题讨论】:
-
TCP 没有消息的概念。它只是一个字节流。您必须在此字节流之上定义消息语义,例如使用长度前缀或消息结束标记等。
-
我想我正在寻找的是一种阅读到目前为止已发送的所有内容的方法。我正在阅读 HTTP 消息,因此我可以解析字段并以这种方式进行操作 - 只是惊讶地没有一种简单的方法来读取它在需要挂起时返回。
-
"我正在阅读 HTTP 消息" - HTTP 具有明确定义的消息格式。如果正文是通过 Content-Length 在 HTTP 标头中给出的,或者在传输编码在每个正文块之前分块的情况下给出的长度。标头本身在其结束位置(空行)有一个清晰的标记。不要错误地猜测协议是如何工作的,然后想知道为什么它这么难。而是查看实际标准并使用为处理特定协议而创建的库。
-
“读取整个消息......读取整个数据包......” - 您混淆了概念,这些概念实际上与 TCP 无关。消息不是 TCP 中的数据包(尽管它会在 UDP 中)。同一条消息可以通过网络上的多个数据包传递,多个消息可以放入同一个数据包等。