将 IO#pos 和 IO#seek 与 UDPSocket 一起使用

Using IO#pos and IO#seek with UDPSocket

我有一个 UDPSocket 实例:

io = UDPSocket.new
io.connect "8.8.4.4", 53

它通过端口 53 连接到 DNS 服务器,发送 DNS 查询并检索结果。 DNS 具有指针形式的内置消息压缩,请参见 RFC 1035, 4.1.4。消息压缩。资源记录可以包含指向问题域的消息偏移量,而不是域名。这样就不必为每条记录重复域名。

我已经在我的资源记录中实现如下 class:

 def self.from_io(io : IO, format : IO::ByteFormat) : self
    domain = ""
    loop do
      codepoint = UInt8.from_io io, format
      break if codepoint == 0
      if codepoint >= 192 # if the octet starts with 11 as defined in the rfc
        current_pos = io.pos
        pointer = UInt8.from_io io, format
        io.seek(pointer)
        # read the string...
      end
      # ...
    end
end

这不起作用,因为 UDPSocket 没有实现 IO#posIO#seek:

Unhandled exception: Unable to pos

为了解决这个问题,我创建了一个使用 IO::Memory:

的子class
class DNS::DNSSocket < UDPSocket
  def initialize(family : Socket::Family = Socket::Family::INET)
    super family
    @memory = IO::Memory.new
  end

  def read(slice : Bytes)
    if slice.size + pos > @memory.size
      super slice
      @memory.write slice
    else
      @memory.read slice
    end
    slice.size
  end

  def pos
    @memory.pos
  end

  def pos=(value)
    @memory.pos = value
  end

  def seek(offset, whence : Seek = IO::Seek::Set)
    @memory.seek offset, whence
  end

  def clear
    @memory.clear
  end
end

我的问题如下:

  1. 这是一个好的解决方案吗,或者您知道更优雅的方法吗?

  2. IO::Memory 实例需要在每条消息后重置。是否可以从我的 DNSSocket 实现中在数据报(数据包)的末尾或开头调用 clear?我也可以在我的消息解析器中调用它,但我不想这样做。

UDP 是一种无连接通信模型,因此不支持流式传输。 UDPSocket 继承了 IO,但它不应该,我认为这实际上是 API 中的一个缺陷,由 Socket 继承自 IO。它仍然有效,因为 IO 实现使用的底层系统调用也适用于 UDP 套接字。但是使用 UDPSocket 作为 IO 并不理想,应该避免。由于数据报通常都是短消息,因此将它们完全加载到内存中是完全没问题的。

因此,我建议改用 UDPSocket#receive,这样您就可以轻松地将切片包裹在 IO::Memory 中。

补充一下其他回复,解决方法比我想象的简单多了。

slice = Bytes.new(512)
socket.read slice
io = IO::Memory.new slice