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
如果 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 通常使用 NAGL 优化选项创建,这意味着在写入后发送数据之前会有很小的延迟,这样如果在数据包离开之前发送了更多的小数据,它可以将它们合并成一个数据包。
这可能会导致一组数据包中的最后一个数据包产生额外的延迟。 但通过避免多个数据包的开销,可以带来少许加速。
为了解决最后一个数据包的延迟问题,您要么想在写入后立即对您的 Socket 调用 #flush
(最好保留 NAGL,写入大量数据,然后调用 #flush
),要么设置 Socket 选项以禁用 NAGL。 有关更多信息,请参见 Nagle 算法。
服务器(先运行此程序)
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
见[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。