Networking

Ruby’s networking capabilities are provided by the standard library rather than by core classes. For this reason, the subsections that follow do not attempt to enumerate each available class or method. Instead, they demonstrate how to accomplish common networking tasks with simple examples. Use ri for more complete documentation.

At the lowest level, networking is accomplished with sockets, which are a kind of IO object. Once you have a socket opened, you can read data from, or write data to, another computer just as if you were reading from or writing to a file. The socket class hierarchy is somewhat confusing, but the details are not important in the following examples. Internet clients use the TCPSocket class, and Internet servers use the TCPServer class (also a socket). All socket classes are part of the standard library, so to use them in your Ruby program, you must first write:

require 'socket'

A Very Simple Client

To write Internet client applications, use the TCPSocket class. Obtain a TCPSocket instance with the TCPSocket.open class method, or with its synonym TCPSocket.new. Pass the name of the host to connect to as the first argument and the port as the second argument. (The port should be an integer between 1 and 65535, specified as a Fixnum or String object. Different internet protocols use different ports. Web servers use port 80 by default, for example. You may also pass the name of an Internet service, such as “http”, as a string, in place of a port number, but this is not well documented and may be system dependent.)

Once you have a socket open, you can read from it like any IO object. When done, remember to close it, as you would close a file. The following code is a very simple client that connects to a given host and port, reads any available data from the socket, and then exits:

require 'socket'                # Sockets are in standard library

host, port = ARGV               # Host and port from command line

s = TCPSocket.open(host, port)  # Open a socket to host and port
while line = s.gets             # Read lines from the socket
  puts line.chop                # And print with platform line terminator
end
s.close                         # Close the socket when done

Like File.open, the TCPSocket.open method can be invoked with a block. In that form, it passes the open socket to the block and automatically closes the socket when the block returns. So we can also write this code like this:

require 'socket'                  
host, port = ARGV                 
TCPSocket.open(host, port) do |s| # Use block form of open
  while line = s.gets             
    puts line.chop                
  end
end                               # Socket automatically closed

This client code is for use with services like the old-style (and now defunct) Unix “daytime” service. With services like these, the client doesn’t make a query; the client simply connects and the server sends a response. If you can’t find an Internet host running a server to test the client with, don’t despair—the next section shows how to write an equally simple time server.

A Very Simple Server

To write Internet servers, we use the TCPServer class. In essence, a TCPServer object is a factory for TCPSocket objects. Call TCPServer.open to specify a port for your service and create a TCPServer object. Next, call the accept method of the returned TCPServer object. This method waits until a client connects to the port you specified, and then returns a TCPSocket object that represents the connection to that client.

The following code shows how we might write a simple time server. It listens for connections on port 2000. When a client connects to that port, it sends the current time to the client and closes the socket, thereby terminating the connection with the client:

require 'socket'               # Get sockets from stdlib

server = TCPServer.open(2000)  # Socket to listen on port 2000
loop {                         # Infinite loop: servers run forever
  client = server.accept       # Wait for a client to connect
  client.puts(Time.now.ctime)  # Send the time to the client
  client.close                 # Disconnect from the client
}

To test this code, run it in the background or in another terminal window. Then, run the simple client code from above with a command like this:

ruby client.rb localhost 2000

Datagrams

Most Internet protocols are implemented using TCPSocket and TCPServer, as shown earlier. A lower-overhead alternative is to use UDP datagrams, with the UDPSocket class. UDP allows computers to send individual packets of data to other computers, without the overhead of establishing a persistent connection. The following client and server code demonstrate: the client sends a datagram containing a string of text to a specified host and port. The server, which should be running on that host and listening on that port, receives the text, converts it to uppercase (not much of a service, I know), and sends it back in a second datagram.

The client code is first. Note that although UDPSocket objects are IO objects, datagrams are pretty different from other IO streams. For this reason, we avoid using IO methods and use the lower-level sending and receiving methods of UDPSocket. The second argument to the send method specifies flags. It is required, even though we are not setting any flags. The argument to recvfrom specifies the maximum amount of data we are interested in receiving. In this case, we limit our client and server to transferring 1 kilobyte:

require 'socket'                     # Standard library

host, port, request = ARGV           # Get args from command line

ds = UDPSocket.new                   # Create datagram socket
ds.connect(host, port)               # Connect to the port on the host
ds.send(request, 0)                  # Send the request text
response,address = ds.recvfrom(1024) # Wait for a response (1kb max)
puts response                        # Print the response

The server code uses the UDPSocket class just as the client code does—there is no special UDPServer class for datagram-based servers. Instead of calling connect to connect the socket, our server calls bind to tell the socket what port to listen on. The server then uses send and recvfrom, just as the client does, but in the opposite order. It calls recvfrom to wait until it receives a datagram on the specified port. When that happens, it converts the text it receives to uppercase and sends it back. An important point to notice is that the recvfrom method returns two values. The first is the received data. The second is an array containing information about where that data came from. We extract host and port information from that array and use it to send the response back to the client:

require 'socket'                     # Standard library

port = ARGV[0]                       # The port to listen on

ds = UDPSocket.new                   # Create new socket
ds.bind(nil, port)                   # Make it listen on the port
loop do                              # Loop forever
  request,address=ds.recvfrom(1024)  # Wait to receive something
  response = request.upcase          # Convert request text to uppercase
  clientaddr = address[3]            # What ip address sent the request?
  clientname = address[2]            # What is the host name?
  clientport = address[1]            # What port was it sent from
  ds.send(response, 0,               # Send the response back...
          clientaddr, clientport)    # ...where it came from
  # Log the client connection
  puts "Connection from: #{clientname} #{clientaddr} #{clientport}"
end

A More Complex Client

The following code is a more fully developed Internet client in the style of telnet. It connects to the specified host and port and then loops, reading a line of input from the console, sending it to the server, and then reading and printing the server’s response. It demonstrates how to determine the local and remote addresses of the network connection, adds exception handling, and uses the IO methods read_nonblock and readpartial described earlier in this chapter. The code is well-commented and should be self-explanatory:

require 'socket'     # Sockets from standard library

host, port = ARGV    # Network host and port on command line

begin                # Begin for exception handling
  # Give the user some feedback while connecting.
  STDOUT.print "Connecting..."      # Say what we're doing
  STDOUT.flush                      # Make it visible right away
  s = TCPSocket.open(host, port)    # Connect
  STDOUT.puts "done"                # And say we did it

  # Now display information about the connection.
  local, peer = s.addr, s.peeraddr
  STDOUT.print "Connected to #{peer[2]}:#{peer[1]}"
  STDOUT.puts " using local port #{local[1]}"

  # Wait just a bit, to see if the server sends any initial message.
  begin
    sleep(0.5)                      # Wait half a second
    msg = s.read_nonblock(4096)     # Read whatever is ready
    STDOUT.puts msg.chop            # And display it
  rescue SystemCallError
    # If nothing was ready to read, just ignore the exception.
  end

  # Now begin a loop of client/server interaction.
  loop do
    STDOUT.print '> '   # Display prompt for local input
    STDOUT.flush        # Make sure the prompt is visible
    local = STDIN.gets  # Read line from the console
    break if !local     # Quit if no input from console

    s.puts(local)       # Send the line to the server
    s.flush             # Force it out

    # Read the server's response and print out.
    # The server may send more than one line, so use readpartial
    # to read whatever it sends (as long as it all arrives in one chunk).
    response = s.readpartial(4096) # Read server's response
    puts(response.chop)            # Display response to user
  end
rescue           # If anything goes wrong
  puts $!        # Display the exception to the user
ensure           # And no matter what happens
  s.close if s   # Don't forget to close the socket
end

A Multiplexing Server

The simple time server shown earlier in this section never maintained a connection with any client—it would simply tell the client the time and disconnect. Many more sophisticated servers maintain a connection, and in order to be useful, they must allow multiple clients to connect and interact at the same time. One way to do this is with threads—each client runs in its own thread. We’ll see an example of a multithreaded server later in this chapter. The alternative that we’ll consider here is to write a multiplexing server using the Kernel.select method.

When a server has multiple clients connected, it cannot call a blocking method like gets on the socket of any one client. If it blocks waiting for input from one client, it won’t be able to receive input from other clients or accept connections from new clients. The select method solves this problem; it allows us to block on a whole array of IO objects, and returns when there is activity on any one of those objects. The return value of select is an array of arrays of IO objects. The first element of the array is the array of streams (sockets, in this case) that have data to be read (or a connection to be accepted).

With that explanation of select, you should be able to understand the following server code. The service it implements is trivial—it simply reverses each line of client input and sends it back. It is the mechanism for handling multiple connections that is interesting. Note that we use select to monitor both the TCPServer object and each of the client TCPSocket objects. Also note that the server handles the case where a client asks to disconnect as well as the case where the client disconnects unexpectedly:

# This server reads a line of input from a client, reverses
# the line and sends it back. If the client sends the string "quit"
# it disconnects. It uses Kernel.select to handle multiple sessions.

require 'socket'           

server = TCPServer.open(2000) # Listen on port 2000
sockets = [server]            # An array of sockets we'll monitor
log = STDOUT                  # Send log messages to standard out
while true                    # Servers loop forever
  ready = select(sockets)     # Wait for a socket to be ready
  readable = ready[0]         # These sockets are readable

  readable.each do |socket|         # Loop through readable sockets
    if socket == server             # If the server socket is ready
      client = server.accept        # Accept a new client
      sockets << client             # Add it to the set of sockets
      # Tell the client what and where it has connected.
      client.puts "Reversal service v0.01 running on #{Socket.gethostname}"
      # And log the fact that the client connected
      log.puts "Accepted connection from #{client.peeraddr[2]}"
    else                            # Otherwise, a client is ready
      input = socket.gets           # Read input from the client

      # If no input, the client has disconnected
      if !input   
        log.puts "Client on #{socket.peeraddr[2]} disconnected."
        sockets.delete(socket)      # Stop monitoring this socket
        socket.close                # Close it
        next                        # And go on to the next
      end

      input.chop!                   # Trim client's input
      if (input == "quit")          # If the client asks to quit
        socket.puts("Bye!");        # Say goodbye
        log.puts "Closing connection to #{socket.peeraddr[2]}"
        sockets.delete(socket)      # Stop monitoring the socket
        socket.close                # Terminate the session
      else                          # Otherwise, client is not quitting
        socket.puts(input.reverse)  # So reverse input and send it back
      end
    end
  end
end

Fetching Web Pages

We can use the socket library to implement any Internet protocol. Here, for example, is code to fetch the content of a web page:

require 'socket'           # We need sockets
 
host = 'www.example.com'   # The web server
port = 80                  # Default HTTP port
path = "/index.html"       # The file we want 

# This is the HTTP request we send to fetch a file
request = "GET #{path} HTTP/1.0

"

socket = TCPSocket.open(host,port)  # Connect to server
socket.print(request)               # Send request
response = socket.read              # Read complete response
# Split response at first blank line into headers and body
headers,body = response.split("

", 2) 
print body                          # And display it

HTTP is a complex protocol, and the simple code above only really handles straightforward cases. You might prefer to use a prebuilt library like Net::HTTP for working with HTTP. Here is code that does the equivalent of the previous code:

require 'net/http'         # The library we need
host = 'www.example.com'   # The web server
path = '/index.html'       # The file we want 

http = Net::HTTP.new(host)      # Create a connection
headers, body = http.get(path)  # Request the file
if headers.code == "200"        # Check the status code   
                                # NOTE: code is not a number!
  print body                    # Print body if we got it
else                            # Otherwise
  puts "#{headers.code} #{headers.message}" # Display error message
end

Similar libraries exist for working with the FTP, SMTP, POP, and IMAP protocols. Details of those standard libraries are beyond the scope of this book.

Finally, recall that the open-uri library described earlier in the chapter makes fetching a web page even easier:

require 'open-uri'
open("http://www.example.com/index.html") {|f|
  puts f.read
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.174.191