Ruby Programming/Reference/Objects/Socket
Socket
editThe Socket family of classes is the means that Ruby's standard library uses by default to communicate using networks.
The typical use flow is to create a socket
require 'socket' a = TCPSocket.new 'some host', 80 # port 80
Then read to and write from a
.
Now, if you try and read from a socket, it will typically block until some data comes in
incoming_string = a.recv(1024) # blocks until data becomes available, or the socket is closed.
You can avoid this blocking call by first checking if there is impending data, to do this you use the select call.
readable,writable,error = IO.select([a], [a], nil, 3) # blocks until the timeout occurs (3 seconds) or until a becomes readable.
At this point if a has any incoming data, readable
will be an array like [a]
, otherwise it will be nil
.
if a is writable, then writable will be an array like [a]
error is typically never used, though it might be useful in certain instances.
The select method is really just a wrapper for the underlying select c call for whatever operating system you're on, though in general the semantics should be the same cross platform.
Another way to read without blocking is to call a.recv_nonblock(1024), which will raise an exception if nothing is available.
Example
editHere's an example:
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
Echo Client/Server
editserver.rb (run this first)
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
Output server:
C:\dev>ruby server.rb received:hi server!
Output client:
C:\dev>ruby client.rb got back:got something--closing now--here is your response message from the server
How to tell when a socket closes
editIf BasicSocket#recv (available to all sockets) returns "" that means that the socket has been closed from the other side. The call to recv raising something like "ECONNRESET" means that the socket was closed, but ungracefully, from the other side.
Keeping a connection alive over time when there is no traffic being sent
editIt's common knowledge that TCP sockets stay active until they're closed by one side or the other. But while they're open but not any transmitting data, it's possible that the connection becomes invalid. For example if the other side (the remote side) has gone away, or been rebooted or what not. It's also possible that intermediate NAT's will timed out your connection after awhile unused, thus also rendering the connection invalid. The real problem with this is that if you don't send any data, and the connection is invalid, you will only discover this fact *after* you send data, later. The notification can be thus delayed.
The fix to this problem is to either, every so often, send a ping message or your own, or to set the TCP_SOCKET keepalive option, like
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
Note that this option works for sockets that have already been opened. Also note from here that it appears to only send a ping every two hours or so.
If you have a timeout on read data (like you require them to send you something within x seconds or you will consider them dead), then you can track the last input using Time.now, then use the select command (with a reasonable timeout). Then, after each select, process the results, then iterate over your connections to see if old ones have become invalid because they haven't sent you data for a long time.
setting socket options
editNote also that many options must be set before a socket is opened.
here is some code demonstrating how to set one.
Flushing
editBy default sockets are typically created with the NAGL optimization option turned on, which means that there is a tiny delay before sending data after a write, which is so that if more small data is sent before the packet leaves, it can combine them into one packet.
This can cause extra latency for the last of a set of packets. But can result in a small speedup by avoiding the overhead of multiple packets.
To get around the latency for the end packet, you'll either want to call #flush
on your sockets immediately after writing (preferably leave NAGL on, write a lot, then call #flush
), or set the socket option to disable NAGL. See Nagle's algorithm for more information.
UDP Socket Example
editserver (run this first):
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
client:
require 'socket' sock = UDPSocket.new data = 'I sent this' sock.send(data, 0, '127.0.0.1', 33333) sock.close
UDP Broadcast Example
editSee [1]
Multicast Example
editLinux
editSee [2].
Windows
editSee [3]
Also note "in Windows the setsockopt call has to come after the bind call." from http://www.ruby-forum.com/topic/133208
Alternatives
editOther ways, such as using the eventmachine gem, also provide Socketed programming, with some benefits like better functionality with many sockets (and some drawbacks). The Rev gem is similar, though written mostly in Ruby, not C.
Fibered
editYou can use sockets with fibers (i.e. single threaded, but multiple connection) by using 1.9 + revactor or neverblock gem.
External Links
edit- www.tutorialspoint.com/ruby/ruby_socket_programming.htm a tutorial
- a tutorial.
- a tutorial