通过 SO_RCVTIMEO 套接字选项在 Ruby 中设置套接字超时 [英] Set socket timeout in Ruby via SO_RCVTIMEO socket option

查看:56
本文介绍了通过 SO_RCVTIMEO 套接字选项在 Ruby 中设置套接字超时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

使用 Ruby 的 Timeout 模块不是一种选择,因为它需要为每个超时生成和加入线程,这可能会变得昂贵.在需要低套接字超时和具有大量线程的应用程序中,它本质上会降低性能.这已经在很多地方被注意到,包括 Stack Overflow.

我已经阅读了 Mike Perham 关于这个主题的优秀文章 此处,为了将问题减少到一个可运行代码文件,创建了一个简单的 TCP 服务器示例,该示例将接收请求,等待请求中发送的时间,然后关闭连接.

客户端创建一个socket,设置接收超时为1秒,然后连接到服务器.客户端告诉服务器在 5 秒后关闭会话然后等待数据.

客户端应该在 1 秒后超时,但在 5 秒后成功关闭连接.

#!/usr/bin/env ruby需要插座"定义超时袜子 = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)# 超时设置为 1 秒timeval = [1, 0].pack("l_2")sock.setsockopt Socket::SOL_SOCKET、Socket::SO_RCVTIMEO、timeval# 连接并告诉服务器等待 5 秒sock.connect(Socket.pack_sockaddr_in(1234, '127.0.0.1'))sock.write("5
")# 等待数据发回开始结果 = sock.recvfrom(1024)将会话关闭"拯救厄尔诺::EAGAIN显示超时!"结尾结尾Thread.new 做服务器 = TCPServer.new(nil, 1234)while (session = server.accept)请求 = session.gets睡眠请求.to_isession.close结尾结尾暂停

我也尝试过用 TCPSocket 做同样的事情(它会自动连接)并且在 redis 等项目.

另外,我可以通过像这样调用 getsockopt 来验证该选项是否已设置:

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

设置这个套接字选项真的适用于任何人吗?

解决方案

您可以使用 Ruby IO 类中的 select高效 做到这一点.

IO::select 有 4 个参数.前三个是要监控的套接字数组,最后一个是超时(以秒为单位).

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

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

  • 准备好阅读
  • 准备写作
  • 有待处理的异常

第四个是你想设置的超时时间(如果有的话).我们将利用这个参数.

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

所以 select 的返回值会是这样的:

<预><代码>[[已准备好读取的套接字],[套接字准备好写入],[套接字引发错误]]

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

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

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

ready = IO.select([socket], nil, nil, timeout)如果准备好了# 读取别的# 引发一些表示超时的东西结尾

这样做的好处是不会为每个超时启动一个新线程(如在 Timeout 模块中),并且将使具有许多超时的多线程应用程序在 Ruby 中更快.

I'm trying to make sockets timeout in Ruby via the SO_RCVTIMEO socket option however it seems to have no effect on any recent *nix operating system.

Using Ruby's Timeout module is not an option as it requires spawning and joining threads for each timeout which can become expensive. In applications that require low socket timeouts and which have a high number of threads it essentially kills performance. This has been noted in many places including Stack Overflow.

I've read Mike Perham's excellent post on the subject here and in an effort to reduce the problem to one file of runnable code created a simple example of a TCP server that will receive a request, wait the amount of time sent in the request and then close the connection.

The client creates a socket, sets the receive timeout to be 1 second, and then connects to the server. The client tells the server to close the session after 5 seconds then waits for data.

The client should timeout after one second but instead successfully closes the connection after 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
")

  # 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

I've tried doing the same thing with a TCPSocket as well (which connects automatically) and have seen similar code in redis and other projects.

Additionally, I can verify that the option has been set by calling getsockopt like this:

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

Does setting this socket option actually work for anyone?

解决方案

You can do this efficiently using select from Ruby's IO class.

IO::select takes 4 parameters. The first three are arrays of sockets to monitor and the last one is a timeout (specified in seconds).

The way select works is that it makes lists of IO objects ready for a given operation by blocking until at least one of them is ready to either be read from, written to, or wants to raise an error.

The first three arguments therefore, correspond to the different types of states to monitor.

  • Ready for reading
  • Ready for writing
  • Has pending exception

The fourth is the timeout you want to set (if any). We are going to take advantage of this parameter.

Select returns an array that contains arrays of IO objects (sockets in this case) which are deemed ready by the operating system for the particular action being monitored.

So the return value of select will look like this:

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

However, select returns nil if the optional timeout value is given and no IO object is ready within timeout seconds.

Therefore, if you want to do performant IO timeouts in Ruby and avoid having to use the Timeout module, you can do the following:

Let's build an example where we wait timeout seconds for a read on socket:

ready = IO.select([socket], nil, nil, timeout)

if ready
  # do the read
else
  # raise something that indicates a timeout
end

This has the benefit of not spinning up a new thread for each timeout (as in the Timeout module) and will make multi-threaded applications with many timeouts much faster in Ruby.

这篇关于通过 SO_RCVTIMEO 套接字选项在 Ruby 中设置套接字超时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆