-->
Page 597
Listing 28.9. nonblock.
1: #!/usr/bin/perl 2: use Socket; 3: use Fcntl; 4: require "./network.pl"; 5: $poke = "yo!"; 6: $NETFD = makeudpcli(); 7: fcntl $NETFD, &F_SETFL, O_NONBLOCK or die "Fcntl failed : $!\n"; 8: (rest of file remains the same)
A new module, Fcntl, is added to the program in line 3, which provides an interface to the fcntl(2) system call. It is used to alter file descriptor properties, such as blocking and how it handles certain signals. In line 7, the last line of the modifications to client2, you set the O_NONBLOCK flag for the UDP socket. The rest of the program is unchanged.
When nonblocking I/O is used, the application designer has to be very careful when handling errors returned from recv(), send(), and other I/O related functions. When no more data is available for reading or no more data can be written, these functions return error codes. As a result, the application has to be prepared to handle some errors as being routine conditions. This is also true of the C/C++ interfaces.
Frequently, applications need to maintain more than one socket or file descriptor. For example, many system services such as Telnet, rlogin, and FTP are managed by one process on Linux. In order to do this, the process, inetd, listens for requests for these services by opening a socket for each one. Other applications, such as Applix, Netscape, and Xemacs, monitor file descriptors for the keyboard, mouse, and perhaps the network.
Let's set up an example that monitors the keyboard and a network connection. Listing 28.10 is contained in the file udptalk, which is included on the CD-ROM.
Listing 28.10. updtalk.
1: #!/usr/bin/perl 2: 3: use Socket; 4: require "./network.pl"; 5: 6: $NETFD = makeudpserv($ARGV[2]); 7: 8: $addr = gethostbyname($ARGV[0]); 9: $port = getservbyname($ARGV[1], `udp'); 10: 11: $servaddr = sockaddr_in($port, $addr); 12: 13: $rin = ""; 14: vec($rin, fileno(STDIN), 1) = 1;
continues
Page 598
Listing 28.10. continued
15: vec($rin, fileno($NETFD), 1) = 1; 16: 17: while (1) { 18: 19: select $ready = $rin, undef, undef, undef; 20: 21: if (vec($ready, fileno(STDIN), 1) == 1) { 22: sysread STDIN, $mesg, 256; 23: send $NETFD, $mesg, 0, $servaddr; 24: } 25: if (vec($ready, fileno($NETFD), 1) == 1) { 26: recv $NETFD, $netmsg, 256, 0; 27: print "$netmsg"; 28: $netmsg = ""; 29: } 30: } 31: close $NETFD;
In order to test this program it must be run in either two windows on the same system or on two different systems. At one command-line session, execute the following command, where iest is the host on which the second command will be run:
$ ./udptalk iest test test1
On the second host, run the following command, where iest is the host where the first command was run:
$ ./udptalk iest test1 test
Each session will wait for keyboard input. Each line that is typed at one program is printed by the other, after you press Enter.
In order to perform the two-way communication required for this exercise, both instances of udptalk have to bind a well-known port. To permit this on a single workstation, the program accepts two port names as the second and third command-line arguments. For obvious reasons, two programs cannot register interest in the same port.
In line 6 of Listing 28.10, udptalk uses makeudpserv() to create a UDP socket and bind it to a well-known port. For the examples here, I used 8000 for one copy and 8001 for the other.
In lines 8_11, you perform the usual procedure for building a network address. This will be the address to which the keyboard input is written.
Lines 13_15 build bit vectors in preparation for the select() function. In Perl, a bit vector is a scalar variable that is handled as an array of bits; in other words, instead of being evaluated as bytes that add up to characters or numbers, each individual bit is evaluated as a distinct value.
In line 13, you create a variable ($rin) and tell the Perl interpreter to clear it. You then use the vec() and fileno() functions to determine the file number for STDIN (the keyboard) and set
Page 599
that bit in $rin. Then you do the same for the socket created by makeudpcli(). Therefore, if STDIN uses file descriptor 1 (which is generally the case), the second bit in $rin is set to 1. (Bit vectors, like other arrays, start numbering indexes at zero.) Fortunately, the vec() function can be used to read bit vectors also, so you can treat these data structures as opaque (and sleep a lot better at night for not knowing the details).
select() is a key function for systems programmers. Unfortunately, it suffers from an arcane interface that is intimidating in any language. System V UNIX has a replacement, poll(), that is a little easier to use, but it is not available on Linux or within Perl. The following is the function description for select():
select readfds, writefds, exceptfds, timeout;
Like most of the UNIX system interface, this is virtually identical to
select() in C/C++.
select() is used for discovering which file descriptors are ready for reading, are ready for
writing, or have an exceptional condition. An exceptional condition usually corresponds with
the arrival of what is called out-of-band or urgent data. This sort of data is most frequently
associated with TCP connections. When a message is sent out-of-band, it is tagged as being
more important than any previously sent data and is placed at the top of the data queue. A client
or server can use this to notify the process on the other end of a connection that it is
exiting immediately.
The first three arguments are bit vectors that correspond to the file descriptors that you are interested in reading or writing to or that you are monitoring for exceptional conditions. If you aren't interested in a set of file descriptors, you can pass undef instead of a vector. In Listing 28.10, you aren't interested in writing or exceptions, so you pass undef for the second and third arguments.
When select returns, only the bits that correspond to files with activity are set; if any descriptors aren't ready when select returns, their settings are lost in the vector. For that reason, you have select() create a new vector and copy it into $ready. This is done by passing an assignment to select() as the first argument in line 19.
The last parameter is the time-out interval in seconds. select() waits for activity for this period. If the period expires with no activity occurring, select() will return with everything in the vector cleared. Because undef is supplied for timeout in line 19, select() will block until a file is ready.
Inside the while loop entered in line 17, you call select(), passing it the bit vector built earlier and the new one to be created. When it returns, you check the vector using vec() with pretty much the same syntax as you used to set the bits; however because you are using == instead of =, vec() returns the value of the bit instead of setting it.
If the bit for STDIN is set, you read from the keyboard and send it to the other instance of udptalk. If the bit for the socket is set, you read from it and print it to the terminal. This sequence illustrates a very important advantage of the sockets interface. The program is extracting data to and from the network using the same functions as the keyboard and screen.