Networking API for Ruby

 < Day Day Up > 



Ruby includes a rich set of networking APIs that operate at two layers. The standard Sockets API is provided and higher-level Application layer protocols such as HTTP, FTP, SMTP, Telnet, and even CGI are supported.

The Sockets API is defined as a class hierarchy that provides a range of capabilities (see Figure 13.1). All networking classes are derived from the IO base class. Class BasicSocket is the abstract base class from which all other sockets classes are derived. The Socket class is the low-level Sockets API class that mimics the BSD4.4 API. The IPSocket class is the base class for both the TCPSocket class and the UDPSocket class. Finally, the TCPServer class provides specific behaviors for server-side TCP sockets.

click to expand
Figure 13.1: Ruby Sockets API class hierarchy.

Class vs. Instance Methods

In the following sections, we discuss two distinct types of methods, class methods and instance methods. A class method is a method that operates on the class, whereas an instance method operates on an instance of the class. Let’s look at an example using the Socket class. We can use the class method new to create a new socket, which is performed as:

sock = Socket::new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )

which creates a new socket (a stream socket), stored in the instance variable sock. An example of an instance method uses the previously defined sock instance. For example, after we’ve bound this socket with a local address, we could accept new incoming connections with:

clisock = sock.accept 

Just remember that a class method operates directly on the class, whereas an instance method operates on an instance of the class.

Sockets API Summary

We saw in Figure 13.1 that Ruby provides a number of classes for Sockets programming; let’s now look at the classes and methods that are available.

BasicSocket Class

The BasicSocket class provides the base for all other socket classes. It provides a base set of features that are inherited and used through the other socket classes. The BasicSocket class is shown in Figure 13.2.

start figure

Method

Type

Description

BasicSocket::do_no_reverse_lookup

Class

Returns ‘true’ if a DNS query returns a numeric IP address instead of a hostname.

BasicSocket::do_not_reverse_

Class

Sets the behavior for reverse lookup.

getpeername lookup=bool

Instance

Returns the sockaddr_in information for the socket peer.

getsockname

Instance

Returns the sockaddr_in information for the local socket.

Getsockopt( level, optname )

Instance

Returns the option value for the specified socket option.

setsockopt( level, optname, value

Instance

Sets the option value for the specified socket option.

shutdown( [howl] )

Instance

Performs a shutdown of the socket instance.

recv( len, [flags] )

Instance

Receives data from the socket and returns a string.

send( buffer, flags [, to] )

Instance

Sends data through a socket. The optional to represents a sockaddr_in binary string.

end figure

Figure 13.2: BasicSocket class methods. (Adapted from [Matsumoto01] Matsumoto, Yukihiro, Ruby in a Nutshell, O’Reilly & Associates, 2001.)

All methods provided by the BasicSocket class are accessible by instances in all other networking classes (because all are derived from the BasicSocket class).

Socket Class

The Socket class provides the closest abstraction to the BSD4.4 API as is possible with Ruby. This class provides the basic set of methods, most of which are synonymous with the standard Sockets API functions. The methods provided in the Sockets class are shown in Figure 13.3.

start figure

Method

Type

Description

Socket::getaddrinfo( host, port [, family[, type[, proto[,flags]]]] )

Class

Create a sockaddr_in structure using the port [, family[, type[, proto[,flags]]]] )provided data.

Socket::gethostbyaddr( addr [, type] )

Class

Returns a sockaddr_in structure for the address.

Socket::gethostbyname( name)

Class

Returns an array containing host information for name.

Socket::gethostname

Class

Returns a string of the local hostname.

Socket::getnameinfo( addr[,flags] )

Class

Returns an array containing name information for the defined address.

Socket::getservbyname( service[,proto] )

Class

Return the port number for the defined service.

Socket::new( domain, type, proto )Socket::open( domain, type, proto )

Class

Create a new socket.

accept

Instance

Accept a client connection through a server socket.

addr

Instance

Returns a sockaddr_in structure.

bind( addr )

Instance

Binds the socket to the address information contained in addr (sockaddr_in).

connect( addr )

Instance

Connects the client socket to the server defined in addr.

listen( backlog )

Instance

Put the server socket into the listening state with an established queue of size backlog.

recvfrom( len[, flags] )

Instance

Returns the data and source address information in an array.

peeraddr

Instance

Returns a sockaddr_in structure for the peer socket.

end figure

Figure 13.3: Socket class methods. (Adapted from [Matsumoto01] Matsumoto, Yukihiro, Ruby in a Nutshell, O’Reilly & Associates, 2001.)

IPSocket Class

The IPSocket class provides the base class for all Internet protocol sockets. For this reason, both TCPSocket and UDPSocket classes are derived from the IPSocket class. The methods provided in the IPSocket class are shown in Figure 13.4.

start figure

Method

Type

Description

IPSocket::getaddress( host )

Class

Resolves the fully qualified domain name to an IP. address.

addr

Instance

Returns the sockaddr_in structure for the local socket.

peeraddr

Instance

Returns the sockaddr_in structure for the peer socket.

recvfrom( len [, flags] )

Instance

Returns the received data from the socket in an array with the source address information.

end figure

Figure 13.4: IPSocket class methods. (Adapted from [Matsumoto01] Matsumoto, Yukihiro, Ruby in a Nutshell, O’Reilly & Associates, 2001.)

Although this book focuses on IPv4, Ruby supports IPv6 if the underlying platform supports it.

UDPSocket Class

The UDPSocket class provides a small set of methods that focus on the User Datagram Protocol (UDP). Although the Sockets class provides everything that is needed for both TCP and UDP Sockets programming, the UDPSocket class provides two simple constructor methods to create UDP sockets simply. The methods provided in the UDPSocket class are shown in Figure 13.5.

start figure

Method

Type

Description

UDPSocket::new( [socktype] ) UDPSocket::open( [socktype] )

Class

Create a new UDP Socket.

bind( host, port )

Instance

Binds the host and port information to the local socket.

connect( host, port )

Instance

Connect the socket to the named port on host.

send( buffer, flags [, to ] ) send( buffer, flags [, host, port ] )

Instance

Send the buffer using a sockaddr_in binary string (to), or a host and port number. Returns sent length.

end figure

Figure 13.5: UDPSocket class methods. (Adapted from [Matsumoto01] Matsumoto, Yukihiro, Ruby in a Nutshell, O’Reilly & Associates, 2001.)

TCPSocket Class

The TCPSocket class provides two methods for simple construction of TCP sockets. These methods are shown in Figure 13.6.

start figure

Method

Type

Description

TCPSocket::new( host, service ) TCPSocket::new( host, service )

Class

Creates a new TCP socket and connects it to service on host. Service may be a string or port number.

Method

Type

Description

UDPSocket::new( [socktype] ) UDPSocket::open( [socktype] )

Class

Create a new UDP Socket.

bind( host, port )

Instance

Binds the host and port information to the local socket.

connect( host, port )

Instance

Connect the socket to the named port on host.

send( buffer, flags [, to ] ) send( buffer, flags [, host, port ] )

Instance

Send the buffer using a sockaddr_in binary string (to), or a host and port number. Returns sent length.

end figure

Figure 13.6: TCPSocket class methods. (Adapted from [Matsumoto01] Matsumoto, Yukihiro, Ruby in a Nutshell, O’Reilly & Associates, 2001.)

Like UDPSocket, the TCPSocket methods could be done using the Sockets class, but with a greater number of method calls to achieve the same result.

TCPServer Class

Finally, the TCPServer class provides a small number of methods specifically for the creation of TCP server sockets. These methods are shown in Figure 13.7.

start figure

Method

Type

Description

TCPServer::new( [host, ] service ) TCPServer::new( [host, ] service )

Class

Creates a new server socket (synonymous with socket/bind/listen).

accept

Instance

Accept a new client connection (blocking call).

end figure

Figure 13.7: TCPServer class methods. (Adapted from [Matsumoto01] Matsumoto, Yukihiro, Ruby in a Nutshell, O’Reilly & Associates, 2001.)

Sockets API Discussion

Let’s now look at how the Ruby Sockets API is used. We exercise many of the methods shown in the previous class method figures, including how using methods from the UDPSocket and TCPServer can greatly simplify Sockets programming.

Creating and Destroying Sockets

As with any socket-based application, the creation of a socket is the first step before communication may occur. Ruby provides a number of methods for socket creation, depending upon the type of socket needed by the developer. We look at the primitive example first, and then move on to Ruby’s more expressive methods that can simplify application development.

Note 

All of the methods discussed in this section require that the application developer make the sockets classes visible. At the beginning of the Ruby application, a line specifying, require 'socket' must be present.

The Socket class provides the primitive standard socket function that is most familiar to C language programmers. This class method can be used to create stream (TCP), datagram (UDP), and even raw sockets. Note that the Socket::new and Socket::open methods achieve the same result.

sock = Socket::new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )

Note the similarities of this function to the C language primitive. The only real differences are the Ruby-specific naming conventions (SOCKET:: to bring the symbol in scope). In this example, we’ve created a stream socket. To create a datagram socket, we could do the following:

sock = Socket::open( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )

Finally, a raw socket could be created with:

sock = Socket::open( Socket::AF_INET, Socket::SOCK_RAW, 0 )

Recall that the Socket::AF_INET symbol defines that we’re creating an Internet protocol socket. The second parameter (type) defines the semantics of the communication (Socket::SOCK_STREAM for TCP, Socket::SOCK_DGRAM for UDP, and Socket::SOCK_RAW for raw IP). The third parameter defines the particular protocol to use, which is useful here only in the Socket::SOCK_RAW case.

Depending upon the protocol being used for the socket, Ruby provides three other mechanisms to not only create the socket, but also to connect or bind/listen depending upon the method. We can create a client TCP socket using:

sock = TCPSocket::open( "192.168.1.2", 13 )

This example not only creates a socket, sock, but also connects the socket to the named host and port number. For a TCP server socket, the TCPServer class can be used:

sock = TCPServer::open( "192.168.1.1", 13 )

This example creates the TCP server socket and then binds the address and port number to the newly created socket. This has the effect of restricting the interface from which connections may be accepted to “192.168.1.1” (with port 13).

To create a UDP socket, we can use the UDPSocket class:

sock = UDPSocket::new( Socket::AF_INET ) sock = UDPSocket::new 

Each of these examples creates a new UDP socket, as the Socket::AF_INET is the default and is not required to be specified.

When we’re finished with a socket, we must close it. To close our previously created socket, sock, we use the close method. The close method is actually part of the IO class, but we inherit it and can, therefore, use it on the socket.

sock::close 

After the close method is called, no further communication is possible with this socket. Any data queued for transmission would be given some amount of time to be sent before the connection physically closes.

Socket Addresses

Unlike C, Ruby has no sockaddr_in structure, but instead has a binary string that represents this structure as an array. This is a little more complicated than dealing with C socket addresses, but luckily, they’re not needed very often.

Because Ruby is interpreted and its networking classes sit on top of the host system’s networking interface, we must map our socket address onto what is expected by the host system. In other words, we have to represent the socket address in the binary form of the sockaddr_in structure. Recall from Chapter 9, Network Programming in the C Language, that this is represented in C as:

struct sockaddr_in {     int16_t sin_family;     uint16_t sin_port;     struct in_addr sin_addr;     char sin_zero[8]; }; struct in_addr {     uint32_t s_addr; };

To map our array to Ruby, the simplest way is to use the pack method of an Array class. Consider the following:

sa = [Socket::AF_INET, 13, 192, 168, 1, 2, 0, 0].pack("snCCCCNN")

Let’s break this down piece by piece. First, we create an array (contained within the square brackets) and assign values representing our socket address. In this case, the sin_family is AF_INET, the sin_port is 13, the sin_addr represents our IP address 192.168.1.2, and the sin_zero is two 32-bit values (ignored). We use the pack method to pack the elements of the array into a string according to the provided template (the argument to the pack method). The template is "snCCCCNN", where each letter represents a different directive. The ‘s’ represents an unsigned short, so Socket::AF_INET is represented by a 16-bit value. The ‘n’ represents a big-endian short so the value 13 (the port number) is byte-swapped before being packed into the string. The ‘C’ represents an unsigned char. Therefore, our IP address (192, 168, 1, 2) is packed as four bytes (note the four ‘C’s) into the binary string. Finally, the ‘N’ directive denotes a big-endian long. The final two zeros in our array satisfy the unused sin_zero character array.

This is synonymous with the following C language fragment:

struct sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_port = htons( 13 ); sa.sin_addr.s_addr = inet_addr("192.168.1.2");

Although C appears a little more straightforward, we could emulate this structure in Ruby. It would be a little less efficient, albeit more readable, but it’s a pure matter of preference.

Socket Primitives

In this section, we look at a number of other important socket control methods. We also investigate the variety of ways that the same results can be achieved using different Ruby methods.

Bind

The bind method provides a local naming capability to a socket. This can be used to name either client or server sockets, but is used most often in the server case. The bind method for the Sockets class is an instance method and is provided by the following prototype:

sock.bind( addr )

where addr is a sockaddr_in structure packed into a binary string and sock is an instance of a socket. Sample usage of this form of bind is:

s = Socket::new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) sa = [Socket::AF_INET, 13, 192, 168, 1, 2, 0, 0].pack("snCCCCNN") s.bind(sa)

In this example, we’re creating a stream server on port 13 that will accept client connections only on the interface defined as 192.168.1.2.

A simpler example utilizes the TCPServer class. Let’s use the TCPServer class to reproduce the previous bind example.

sock = TCPServer::new( "192.168.1.2", 13 )

Knowing that port 13 is the “daytime” service port, we could also utilize the service string to make it even more readable:

sock = TCPServer::new( "192.168.1.2", "daytime" )

If we want to accept client connections from any available interface, we could provide any empty string. This behavior is synonymous to the INADDR_ANY symbolic within our C language examples, for example:

sock = TCPServer::new( "", "daytime" )

Note that we could also pass a 0 as service to allow the networking layer to dynamically assign us a port (ephemeral port). It’s also important to note that along with creating the server socket using TCPServer, we’ve also automatically bound the address and performed the listen method. The developer need only call the accept method to accept a client connection.

Finally, let’s look at a UDP example. The use of bind with a UDP socket uses the bind method provided by the UDPSocket class. The following example creates a UDP socket and binds it with host INADDR_ANY (accept datagrams from any interface) with port 13:

sock = UDPSocket::new( Socket::AF_INET ) sock.bind( "", 13 )

Using the TCPSocket class permits us to create a client socket. The TCPSocket class not only allows us to create a socket, but also allows us to automatically connect to a client socket. For example:

sock = TCPSocket::new( "192.168.1.2", 13 )

Upon return, our socket named sock will have been connected to the server at 192.168.1.2 port 13.

Listen

Before a traditional server socket (one created using the Sockets class) can accept incoming client connections, it must call the listen method to declare this willingness. The listen method is provided by the following prototype:

sock.listen( backlog )

The sock argument represents an instance of the server socket and the backlog argument represents the number of outstanding client connections that may be queued. This method may only be used on sockets created with the Sockets class. Here’s a complete example from socket creation to the listen method.

s = Socket::new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) sa = [Socket::AF_INET, 13, 192, 168, 1, 2, 0, 0].pack("snCCCCNN") s.bind(sa) s.listen( 5 );

Recall that the four lines of Ruby code can be performed in a single line using the TCPServer class, as:

sock = TCPServer::new( "192.168.1.2", 13 ) 

Therefore, Ruby’s helper classes should be used wherever possible to minimize source-code length to make the overall script more readable.

Accept

The accept method is the final call made by servers to accept incoming client connections. Before accept can be invoked, the server socket must be created, a name must be bound to it, and listen must be invoked. There are two accept methods, one in the TCPServer class and one in the Sockets class. Because the method differs depending upon the class upon which it’s invoked, let’s look at both cases.

The accept method in the Sockets class accepts a new client connection and returns an array containing the new client socket and the client address information (packed in a sockaddr_in string):

    s = Socket::new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )     sa = [Socket::AF_INET, 13, 192, 168, 1, 2, 0, 0].pack("snCCCCNN")     s.bind(sa)     s.listen( 5 );     cli = s.accept     cli[0].write("Hello!")     cli[0].close     s.close 

The first element of the array returned from accept is the client socket. The second element is the packed sockaddr_in string. If we wanted to know the client information, we could unpack the string as follows:

sockinfo = cli[1].unpack("snCCCCNN")

Our sockinfo array now contains the unpacked binary string. Element 1 (sockinfo[1]) contains the port number of the client connection and elements 2 through 5 contain the client’s IP address (sockinfo[2] through sockinfo[5]).

The TCPServer method is much simpler. With TCPServer, we simply invoke the accept method on our TCPServer instance, which returns a client socket that we can immediately use. Recall that the Sockets class returned an array; TCPServer returns only the socket so that it can be promptly used. For example, the following code performs the same task as our prior accept illustration example:

    s = TCPServer::new( "192.168.1.2", "daytime" )     cli = s.accept     cli.write("Hello!")     cli.close     s.close 

Connect

The connect method is used by client Sockets applications to connect to a server. Clients must have created a socket and then defined an address structure containing the host and port number to which they want to connect. This is illustrated by the following code segment (which can be used to connect to the previously illustrated accept method example):

s = Socket::new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) sa = [Socket::AF_INET, 13, 192, 168, 1, 2, 0, 0].pack("snCCCCNN") s.connect( sa ) print s.recv( 100 ) s.close 

We create our address structure (as with previous examples) and then pass this to our connect method using our client socket instance.

Note the symmetry between the socket address arrays in this example and the server example shown previously with the accept method. There is perfect symmetry because the address structure used to name the server is the same address structure that is needed to connect to the server from the client.

The connect method (for TCP) blocks until either an error occurs, or the three-way handshake with the server completes. For datagram sockets (UDP), the method binds the peer locally so that the peer address isn’t required to be specified for every send.

Sockets I/O

A variety of API methods exists to read data from a socket or write data to a socket. Some methods are class specific, and others are inherited from superclasses such as IO. First, we look at reading and writing from connected sockets and then investigate unconnected (datagram-specific) socket communication.

A number of methods are provided to communicate through a socket. Some of the functions include recv, send, recvfrom, gets, and puts. We look at examples of each of these functions in the following sections. Numerous other IO methods can be used, but for the purposes of brevity, they won’t be covered here.

Stream Sockets

Stream sockets can utilize all of the previously mentioned I/O methods except for the datagram-specific methods, recvfrom and certain variations of send. Let’s now look at some sample code that illustrates the stream-specific methods. The first example illustrates a simple echo server built from the TCPServer class (acctest.rb):

    require 'socket'     serv = TCPServer::new( "192.168.1.1", 45000 )     while true         cli = serv.accept         cli.send( cli.recv( 100 ), 0 )         cli.close     end     serv.close 

In this example, we open a server socket and then await a client connection, storing the newly created client socket in cli. We then simply send what we receive through the client socket back to the source and close the socket. The process then repeats.

Now, let’s look at the TCP client socket that will connect to the previously defined server (clitest.rb):

require 'socket' sock = TCPSocket::open( "192.168.1.1", 45000 ) sock.send( "Hello\n", 0 ) mystring = sock.recv( 100 ) puts mystring sock.close 

After we create our TCP client socket, we immediately send our test string to the server using the send method. Knowing that the server will echo our transmission, we await the response with the recv method. We then simply put the string to standard-out using puts and close the socket using the close method.

That’s simple so far. Now, let’s try the gets and puts methods. These methods get and put a single line from the IO (in this case, a socket). If we were trying to transfer more than one line around, this would become more complicated. Nevertheless, for our example of sending a single line, this works fine. The TCP server now looks like (acctest2.rb):

    require 'socket'     serv = TCPServer::new( "192.168.1.1", 45000 )     while true         cli = serv.accept         cli.puts( cli.gets )         cli.close     end     serv.close 

Our client changes similarly (clitest2.rb):

    require 'socket'     sock = TCPSocket::open( "192.168.1.1", 45000 )     sock.puts( "Hello\n" )     mystring = sock.gets     puts mystring     sock.close 

After sending our single line with the puts method, we collect the response from the server with the gets method.

Datagram Sockets

The recvfrom method is used exclusively by datagram sockets, in addition to the send variation that includes source information. What differentiates these calls from our previously discussed stream calls is that these calls include addressing information. Because datagrams are not connected, we must define the destination address explicitly. Conversely, when receiving a datagram, the address information is also provided so that the source can be identified.

Let’s look at a datagram server and client that provide the echo functionality illustrated previously by our stream socket examples. Our datagram server takes on a slightly different form (dgramsrv.rb):

require 'socket' serv = UDPSocket::new( Socket::AF_INET ) serv.bind( "192.168.1.2", 45000 ) while true     reply, from = serv.recvfrom( 100, 0 )     serv.send( reply, 0, from[2], from[1] ) end serv.close 

After creating a new UDPSocket, we bind the instance of this socket with the interface from which we want to accept client connections (“192.168.1.2”) and the port 45000. In our loop, we then receive datagrams using the recvfrom call. Note the semantics of this call; the method returns not one parameter, but two. The two parameters returned represent the array of data (reply) and the source address of the datagram (from, an array). Element two of the array contains the source host and element one contains the port number. Note that we use these elements when returning the datagram back to the source using the send method. Returning to the recvfrom call, we must also specify the maximum length of data that we want to receive and any flags that must be specified. Similarly, the send method includes flags as the second parameter (though none are defined in either case here).

The datagram client utilizes the datagram send variant with the standard recv method (dgramcli.rb). We can use the standard recv here because we’re not interested in the source of the datagram. If we needed to know the source of the datagram, then the recvfrom method would need to be used.

require 'socket' cli = UDPSocket::new( Socket::AF_INET ) cli.send( "Hello\n", 0, "192.168.1.2", 45000 ) puts cli.recv( 100 ) cli.close 

In the datagram client, after we’ve created our datagram socket, we send our string to the echo server. The echo server is defined in the send method as the IP address and port number combination (“192.168.1.2”, 45000). We then await the response echo datagram using the recv method, and emit it to the terminal using the puts method.

Socket Options

Socket options permit an application to change some of the modifiable behaviors of sockets and the methods that manipulate them. For example, an application can modify the sizes of the send or receive socket buffers or the size of the maximum segment used by the TCP layer for a given socket.

Socket options are a bit more complicated than dealing with options in the C environment. This is because we’re operating in a scripting environment that must interface with the host environment and its corresponding structures.

Let’s look at a simple example first. Let’s say that we want to identify the size of the receive buffer for a given socket. This can be done with the following code segment (sockopt1.rb):

require 'socket' sock = TCPServer::new(0) sz = sock.getsockopt( Socket::SOL_SOCKET, Socket::SO_RCVBUF ) p sz.unpack("i")

First, we create a new stream socket. The 0 argument to the new method forces the stack to define our interface/port pair (which ends up being INADDR_ANY with a random port assignment).

We specify the Socket::SOL_SOCKET argument because we’re interested in a Sockets layer option (as compared to a TCP layer option). For the receiver buffer size, we specify Socket::SO_RCVBUF.

To identify the actual value returned by getsockopt, we unpack the returned array using the unpack method. For this socket option, it will contain an integer, so we specify “i”. The p method permits us to emit the value that’s returned and is an invaluable debugging tool.

Now, let’s look at how an option is set for a given socket, specifically, the linger option (sockopt2.rb). Socket linger allows us to change the behavior of a stream socket when the socket is closed and data is remaining to be sent. After close is called, any data remaining will attempt to be sent for some amount of time. If after some duration, the data cannot be sent, then the data to be sent is abandoned. The time after the close to when the data is removed from the send queue is defined as the linger time. In Ruby, we must construct the structure that is expected by the host environment.

require 'socket' sock = TCPServer::new(0) ling = [1, 10].pack("i2") sock.setsockopt( Socket::SOL_SOCKET, Socket::SO_LINGER, ling )

The linger structure contains first an enable (1 for enable, 0 for disable) and then the time for linger in seconds. In our example, we’re enabling linger and setting the linger value to 10 seconds (packing the structure into two 32-bit words, the structure that is expected by the host environment). We could read back what was configured by:

outval = sock.getsockopt( Socket::SOL_SOCKET, Socket::SO_LINGER ) p outval.unpack( "i2" )

Upon reading the linger array using the getsockopt method, we unpack the array using the “i2” template. This specifies that the two 32-bit integers are packed into the array.

Other Miscellaneous Functions

Let’s now look at a few miscellaneous functions from the various Sockets APIs and the capabilities they provide. The first methods that we discuss provide information about the current host. Method gethostname (of the Socket class) returns the string name of the host:

str = Socket::gethostname puts str

The DNS resolver permits us to resolve a host name to an IP address, or vice versa. Method gethostbyname provides name resolution given an IP address, for example:

str = Socket::gethostbyname("192.168.1.1") puts str[0]

where the first element of the return string represents the FQDN of the IP address. What if we wanted to go in the opposite direction, providing a FQDN and returning an IP address? To achieve this, we use the gethostbyaddr:

str = Socket::gethostbyaddr( "www.microsoft.com", "http" ) puts str[0][3]

The IP address string is contained within the third element of the first array (in String format). To see the format of the returned array, simply use:

p str

The ‘p’ method will emit the object in debugging format.

Now, let’s consider the problem of identifying the port number for a given service. Most of the Ruby methods permit us to specify not only a service string, such as “http”, but also the raw port number, such as 80. To specifically retrieve the port number associated with a service, we use the getservbyname method.

portnum = Socket::getservbyname("http") 

which would return, in this case, 80.

Notification

As a final topic for Ruby, let’s look at the concept of event notification for sockets. This capability is commonly provided by the select primitive, which is an instance method in Ruby’s IO class (and, therefore, inherited by Sockets classes).

As we saw with C, the descriptors representing our IO channels are provided to the select method to identify when some event occurs. We can configure the select method to tell us when a channel is readable, writable, or if an error occurs on it. Further, we can also tell the select method to return after a configurable number of seconds if no event occurs. Consider the following Ruby TCP server in Listing 13.1 (seltest.rb).

Listing 13.1 Ruby TCP server illustrating the select method.

start example
require 'socket' serv = Socket::new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) serv.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 ) sa = [Socket::AF_INET, 45000,         192, 168, 1, 2, 0, 0].pack("snCCCCNN") serv.bind( sa ) serv.listen( 1 ) # Server loop while true   cli = serv.accept   fd = cli[0].to_io   # Client loop   while true     res = select( [fd], nil, nil, 5 )     if res != nil       res = res[0]       if res.include?(fd)         str = fd.gets         fd.puts str       end     else       fd.puts "Timeout!"     end   end end
end example

This very simple server awaits a client connection and then upon receiving one will echo whatever it receives from the client. To determine when the client has sent something to be echoed, we use the select method. The select method works only with IO objects, so we must first convert our socket descriptor (cli) to an IO object using the to_io method. The result is the IO object fd, which we’ll use exclusively for communicating with the client. In the select method, we specify three arrays representing our request. The first is an array of IO objects for which we want to be notified if a read event is generated (data is available on the IO object for read). The second is an array of IO objects for write events, and the third is for error events. The last number represents how many seconds to await an event before timing out.

The return of select is an array of file descriptors for which an event was generated. Note that in this case we use res[0] for the read events. For the write events, we use res[1], and for error events, we use res[2]. These arrays contain the file descriptors for which events fired. We use the include? method to test the array to see if the file descriptor is a member. If the file descriptor is a member of the array, then the event was generated for the socket.

Knowing that the event was generated, we simply read a line from the socket (represented by the new file descriptor, fd) and then write this back out to the client. If the timeout arrives before any event, we notify the peer of this event by writing “Timeout!” to the client.



 < Day Day Up > 



BSD Sockets Programming from a Multi-Language Perspective
Network Programming for Microsoft Windows , Second Edition (Microsoft Programming Series)
ISBN: 1584502681
EAN: 2147483647
Year: 2003
Pages: 225
Authors: Jim Ohlund

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net