通过SO_RCVTIMEO套接字选项在Ruby中设置套接字超时

我试图通过SO_RCVTIMEO套接字选项在Ruby中使套接字超时,但它似乎对任何最近的* nix操作系统没有影响。

使用Ruby的Timeout模块不是一个选项,因为它需要为每个超时生成并加入线程,这可能会变得很昂贵。 在需要低套接字超时且具有大量线程的应用程序中,它基本上会导致性能下降。 在许多地方都已经注意到这一点,包括Stack Overflow 。

我已经阅读了Mike Perham关于此主题的优秀post,并努力将问题减少到一个可运行代码文件,创建了一个将接收请求的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 

我也试过用TCPSocket做同样的事情(自动连接),并在redis和其他项目中看到过类似的代码。

另外,我可以通过调用getsockoptvalidation是否已设置该选项:

 sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO).inspect 

设置此套接字选项实际上适用于任何人吗?

您可以使用Ruby的IO类中的select有效地执行此操作。

IO::select需要4个参数。 前三个是要监视的套接字数组,最后一个是超时(以秒为单位指定)。

select的工作方式是通过阻塞使IO对象的列表为给定的操作做好准备,直到它们中的至少一个准备好被读取,写入或想要引发错误。

因此,前三个参数对应于要监视的不同类型的状态。

  • 准备好阅读
  • 准备写作
  • 有待处理的例外

第四个是您要设置的超时(如果有)。 我们将利用此参数。

Select返回一个包含IO对象数组(在本例中为套接字)的数组,操作系统认为这些数组对于正在监视的特定操作是准备好的。

所以select的返回值如下所示:

 [ [sockets ready for reading], [sockets ready for writing], [sockets raising errors] ] 

但是,如果给出了可选的超时值并且在超时秒内没有准备好IO对象,则select返回nil

因此,如果要在Ruby中执行高性能IO超时并避免使用Timeout模块,则可以执行以下操作:

让我们构建一个示例,我们等待读取socket timeout秒数:

 ready = IO.select([socket], nil, nil, timeout) if ready # do the read else # raise something that indicates a timeout end 

这样做的好处是不会为每个超时启动一个新线程(如Timeout模块中那样),并且可以使Ruby中的multithreading应用程序具有更快的超时速度。

我觉得你基本上没有运气了。 当我使用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 

strace还显示阻止程序的内容。 这是我在服务器超时之前在屏幕上看到的行:

 [pid 5958] ppoll([{fd=5, events=POLLIN}], 1, NULL, NULL, 8 

这意味着程序阻止了对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 

这段代码在这里工作。 当然:这是一个丑陋的解决方案,它不适用于许多平台,等等。它可能是一个出路。

另请注意,这是我第一次在Ruby中做类似的事情,所以我不知道我可能忽略的所有陷阱 – 特别是,我怀疑我在'long read(int, void *, long)'指定的类型'long read(int, void *, long)'和我通过缓冲区读取的方式。

基于我的测试,以及Jesse Storimer关于“使用TCP套接字”的优秀电子书(在Ruby中),超时套接字选项在Ruby 1.9中不起作用 (我认为是2.0和2.1)。 杰西说:

您的操作系统还提供本机套接字超时,可通过SNDTIMEO和RCVTIMEO套接字选项进行设置。 但是,从Ruby 1.9开始,这个function已不再具备function。“

哇。 我认为故事的寓意是忘记这些选项并使用IO.select或Tony IO.select的NIO库。