Tyl*_*ock 19 ruby sockets io timeout tcp-ip
我试图通过SO_RCVTIMEO套接字选项在Ruby中使套接字超时,但它似乎对任何最近的*nix操作系统没有影响.
使用Ruby的Timeout模块不是一个选项,因为它需要为每个超时生成并加入线程,这可能会变得很昂贵.在需要低套接字超时且具有大量线程的应用程序中,它基本上会导致性能下降.许多地方都已经注意到这一点,包括Stack Overflow.
我读过迈克·佩勒姆对这个问题的优秀岗位在这里,并在努力把问题缩小到可运行代码的一个文件中创建一个TCP服务器的一个简单的例子,将收到的请求,等待在请求中发送的时间量和然后关闭连接.
客户端创建套接字,将接收超时设置为1秒,然后连接到服务器.客户端告诉服务器在5秒后关闭会话,然后等待数据.
客户端应在一秒钟后超时,但在5之后成功关闭连接.
#!/usr/bin/env ruby
require 'socket'
def timeout
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
# Timeout set to 1 second
timeval = [1, 0].pack("l_2")
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeval
# Connect and tell the server to wait 5 seconds
sock.connect(Socket.pack_sockaddr_in(1234, '127.0.0.1'))
sock.write("5\n")
# Wait for data to be sent back
begin
result = sock.recvfrom(1024)
puts "session closed"
rescue Errno::EAGAIN
puts "timed out!"
end
end
Thread.new do
server = TCPServer.new(nil, 1234)
while (session = server.accept)
request = session.gets
sleep request.to_i
session.close
end
end
timeout
Run Code Online (Sandbox Code Playgroud)
我也试过用TCPSocket做同样的事情(它会自动连接),并在redis和其他项目中看到类似的代码.
另外,我可以通过这样调用验证是否已设置该选项getsockopt:
sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO).inspect
Run Code Online (Sandbox Code Playgroud)
设置此套接字选项实际上适用于任何人吗?
Tyl*_*ock 24
您可以使用Ruby的IO类有效地执行此操作select.
IO::select需要4个参数.前三个是要监视的套接字数组,最后一个是超时(以秒为单位指定).
select的工作方式是通过阻塞使IO对象列表为给定操作做好准备,直到它们中的至少一个准备好被读取,写入或想要引发错误.
因此,前三个参数对应于要监视的不同类型的状态.
第四个是您要设置的超时(如果有).我们将利用此参数.
Select返回一个数组,该数组包含IO对象数组(在本例中为套接字),操作系统认为这些数组对于要监视的特定操作是准备好的.
所以select的返回值如下所示:
[
[sockets ready for reading],
[sockets ready for writing],
[sockets raising errors]
]
Run Code Online (Sandbox Code Playgroud)
但是,nil如果给出了可选的超时值并且在超时秒内没有准备好IO对象,则select返回.
因此,如果要在Ruby中执行高性能IO超时并避免使用Timeout模块,则可以执行以下操作:
让我们构建一个示例,我们等待timeout读取数秒socket:
ready = IO.select([socket], nil, nil, timeout)
if ready
# do the read
else
# raise something that indicates a timeout
end
Run Code Online (Sandbox Code Playgroud)
这有不抽丝为每个超时一个新的线程(如超时模块)的利益,并会与许多超时多线程应用程序多用Ruby更快.
我觉得你基本上不走运了.当我运行你的例子strace(仅使用外部服务器保持输出清洁)时,很容易检查setsockopt确实被调用:
$ strace -f ruby foo.rb 2>&1 | grep setsockopt
[pid 5833] setsockopt(5, SOL_SOCKET, SO_RCVTIMEO, "\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) = 0
Run Code Online (Sandbox Code Playgroud)
strace还显示了什么阻止了该计划.这是我在服务器超时之前在屏幕上看到的行:
[pid 5958] ppoll([{fd=5, events=POLLIN}], 1, NULL, NULL, 8
Run Code Online (Sandbox Code Playgroud)
这意味着该程序阻止此呼叫ppoll,而不是呼叫recvfrom.列出套接字选项的手册页(socket(7))指出:
超时对select(2),poll(2),epoll_wait(2)等没有影响.
所以超时正在设置但没有效果.我希望我在这里错了,但似乎没有办法在Ruby中改变这种行为.我快速浏览了一下实现,但没有找到明显的出路.再说一次,我希望我错了 - 这似乎是基本的,怎么会不存在?
一个(非常丑陋)的解决方法是使用或直接dl调用.这些调用受您设置的超时影响.例如:readrecvfrom
require 'socket'
require 'dl'
require 'dl/import'
module LibC
extend DL::Importer
dlload 'libc.so.6'
extern 'long read(int, void *, long)'
end
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
timeval = [3, 0].pack("l_l_")
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeval
sock.connect( Socket.pack_sockaddr_in(1234, '127.0.0.1'))
buf = "\0" * 1024
count = LibC.read(sock.fileno, buf, 1024)
if count == -1
puts 'Timeout'
end
Run Code Online (Sandbox Code Playgroud)
这段代码在这里工作.当然:这是一个丑陋的解决方案,它不适用于许多平台,等等.它可能是一个出路.
还请注意,这是我第一次在Ruby中做类似的事情,所以我不知道我可能忽略的所有陷阱 - 特别是,我怀疑我指定的类型'long read(int, void *, long)'和我的方式传递一个缓冲区来阅读.
基于我的测试,以及Jesse Storimer关于"使用TCP套接字"的优秀电子书(在Ruby中),超时套接字选项在Ruby 1.9中不起作用(我认为是2.0和2.1).杰西说:
您的操作系统还提供本机套接字超时,可通过SNDTIMEO和RCVTIMEO套接字选项进行设置.但是,从Ruby 1.9开始,这个功能已不再具备功能."
哇.我认为故事的寓意是忘记这些选项并使用IO.selectTony Arcieri的NIO库.
| 归档时间: |
|
| 查看次数: |
6793 次 |
| 最近记录: |