跳转到内容

Ruby 编程/参考/对象/Socket

来自维基教科书,开放世界中的开放书籍

Socket 类族是 Ruby 标准库默认使用的网络通信方式。

典型的使用流程是创建一个 Socket

require 'socket'
a = TCPSocket.new 'some host', 80 # port 80

然后从 a 中读取和写入。

现在,如果您尝试从 Socket 中读取,它通常会阻塞,直到一些数据传入。

incoming_string = a.recv(1024) # blocks until data becomes available, or the socket is closed.

您可以通过首先检查是否有即将到来的数据来避免这种阻塞调用,为此,您使用 select 调用。

readable,writable,error = IO.select([a], [a], nil, 3) # blocks until the timeout occurs (3 seconds) or until a becomes readable.

此时,如果 a 有任何传入数据,readable 将是一个类似于 [a] 的数组,否则它将是 nil。 如果 a 可写,则 writable 将是一个类似于 [a] 的数组

error 通常不会使用,尽管它在某些情况下可能有用。

select 方法实际上只是对您所使用的任何操作系统的底层 select c 调用的封装,尽管总体上跨平台的语义应该相同。

另一种不阻塞读取的方法是调用 a.recv_nonblock(1024),如果不可用,它将引发异常。

这是一个例子

 require 'socket'
 a = TCPSocket.new 'google.com', 80
 a.write "GET / HTTP/1.0\r\n\r\n"
 begin
   r,w,e = select([a], [w], nil) # r will contain those that are ready to be read from [if they read "" that means the socket was closed], 
                                 # w those ready to write to, e is hardly ever used--not actually sure when it is EVER used.
 rescue SystemCallError => e # this will rescue a multitude of network related errors, like Errno::ENETDOWN, etc.
 end

回显客户端/服务器

[编辑 | 编辑源代码]

server.rb(先运行此程序)

require 'socket'
a = TCPServer.new('', 3333) # '' means to bind to "all interfaces", same as nil or '0.0.0.0'
loop {
  connection = a.accept
  puts "received:" + connection.recv(1024)
  connection.write 'got something--closing now--here is your response message from the server'
  connection.close
}

client.rb

require 'socket'
a = TCPSocket.new('127.0.0.1', 3333) # could replace 127.0.0.1 with your "real" IP if desired.
a.write "hi server!"
puts "got back:" + a.recv(1024)
a.close

输出服务器

C:\dev>ruby server.rb
received:hi server!

输出客户端

C:\dev>ruby client.rb
got back:got something--closing now--here is your response message from the server

如何判断 Socket 何时关闭

[编辑 | 编辑源代码]

如果 BasicSocket#recv(对所有 Socket 都可用)返回 "",则表示 Socket 已从另一端关闭。 recv 调用引发类似“ECONNRESET”的东西意味着 Socket 已关闭,但从另一端不正常地关闭。

在没有数据传输的情况下保持连接一段时间

[编辑 | 编辑源代码]

众所周知,TCP Socket 会一直保持活动状态,直到它们被一方或另一方关闭。 但是,当它们处于打开但没有传输任何数据的状态时,连接可能会变为无效。 例如,如果另一端(远程端)已消失或已重新启动等等。 还有可能中间的 NAT 会在一段时间不使用后超时您的连接,从而也使连接无效。 这个问题的真正问题是,如果您不发送任何数据,并且连接无效,您只有在 *之后* 发送数据时才会发现这一事实。 因此,通知可能会延迟。

解决此问题的方法是,定期发送 ping 消息或您自己的消息,或者设置 TCP_SOCKET keepalive 选项,例如

socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)

请注意,此选项适用于已经打开的 Socket。 另请注意,从 这里 看来它似乎每隔两个小时左右才会发送一次 ping。

如果您在读取数据时有超时(例如,您要求他们在 x 秒内向您发送一些东西,否则您将认为他们已死),那么您可以使用 Time.now 跟踪最后一次输入,然后使用 select 命令(使用合理的超时)。 然后,在每次 select 之后,处理结果,然后遍历您的连接,查看旧连接是否已失效,因为它们长时间没有向您发送数据。

设置 Socket 选项

[编辑 | 编辑源代码]

另请注意,许多选项必须在 打开 Socket 之前设置。

这里 有一些代码演示如何设置一个。

默认情况下,Socket 通常使用 NAGL 优化选项创建,这意味着在写入后发送数据之前会有很小的延迟,这样如果在数据包离开之前发送了更多的小数据,它可以将它们合并成一个数据包。

这可能会导致一组数据包中的最后一个数据包产生额外的延迟。 但通过避免多个数据包的开销,可以带来少许加速。

为了解决最后一个数据包的延迟问题,您要么想在写入后立即对您的 Socket 调用 #flush(最好保留 NAGL,写入大量数据,然后调用 #flush),要么设置 Socket 选项以禁用 NAGL。 有关更多信息,请参见 Nagle 算法

UDP Socket 示例

[编辑 | 编辑源代码]

服务器(先运行此程序)

require 'socket'
BasicSocket.do_not_reverse_lookup = true
# Create socket and bind to address
client = UDPSocket.new
client.bind('0.0.0.0', 33333)
data, addr = client.recvfrom(1024) # if this number is too low it will drop the larger packets and never give them to you
puts "From addr: '%s', msg: '%s'" % [addr.join(','), data]
client.close

客户端

require 'socket'
sock = UDPSocket.new
data = 'I sent this'
sock.send(data, 0, '127.0.0.1', 33333)
sock.close

UDP 广播示例

[编辑 | 编辑源代码]

[1]

组播示例

[编辑 | 编辑源代码]

[2]

[3]

另请注意,来自 http://www.ruby-forum.com/topic/133208 的“在 Windows 中,setsockopt 调用必须在 bind 调用之后进行”。

替代方案

[编辑 | 编辑源代码]

其他方法,例如使用 eventmachine gem,也提供了 Socket 编程,具有一些优势,例如对许多 Socket 的更好的功能(以及一些缺点)。 Rev gem 类似,但主要用 Ruby 编写,而不是用 C 编写。

您可以使用套接字与纤维(即单线程,但多连接)一起使用,方法是使用 1.9 + revactor 或 neverblock gem。


[编辑 | 编辑源代码]
  1. http://betterlogic.com/roger/?p=1646
  2. http://onestepback.org/index.cgi/Tech/Ruby/MulticastingInRuby.red
  3. http://www.ruby-forum.com/topic/200353
华夏公益教科书