将 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#pos
和 IO#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
我的问题如下:
这是一个好的解决方案吗,或者您知道更优雅的方法吗?
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
我有一个 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#pos
和 IO#seek
:
Unhandled exception: Unable to pos
为了解决这个问题,我创建了一个使用 IO::Memory
:
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
我的问题如下:
这是一个好的解决方案吗,或者您知道更优雅的方法吗?
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