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。