Using TCP Urgent Data We will write a client/server pair to illustrate the basics of urgent data. The client connects to the server via TCP and sends a stream of lines containing normal (nonurgent) data, with a small pause between each line. The server reads the lines and prints them to standard output. The twist is that the client intercepts interrupt key (^C) presses and sends out 1 byte of urgent data containing the character "!" . The server retrieves the urgent data when it comes in and prints a warning message to that effect. We will look at the client first. The urg_send.pl script is listed in Figure 17.2. Figure 17.2. Simple urgent data sender client Lines 1 “6: Set up the socket We create a socket connected to the indicated host and port. Lines 7 “10: Install signal handlers We install an INT handler that prints a warning message and then sends a byte of urgent data across the socket using this idiom: send($socket,"!",MSG_OOB); We also want to be able to quit the program, so we trap the QUIT with a signal handler that calls exit() . On UNIX systems, the QUIT signal is usually issued by pressing "^\" (control-backslash). Lines 10 “15: Main loop The remainder of the program is just a loop that writes the string "normal data XX...\n" to the server, where XX is incremented by one each time through the loop. After each call to syswrite() , the loop pauses for 1 second. The odd construction 1 until sleep 1 guarantees that the script sleeps for a minimum of 1 second each time through the loop. Otherwise , every time we press the interrupt key, sleep() is terminated prematurely and we don't get writes that are spaced evenly. When we run the client, it runs for thirty iterations (about 30 s) and quits. If we hit the interrupt key a couple of times during that period, we see the following messages: % urg_send.pl sending 2 bytes of normal data: aa sending 2 bytes of normal data: ab sending 1 byte of OOB data! sending 2 bytes of normal data: ac sending 2 bytes of normal data: ad sending 2 bytes of normal data: ae sending 1 byte of OOB data! sending 2 bytes of normal data: af Now we turn our attention to the server (Figure 17.3), which is only a bit more complicated than the client. The server installs an URG handler that will be invoked whenever urgent data arrives on the socket. However, in order for the operating system to know to deliver the URG signal, we must associate our process ID (PID) with the socket by calling fcntl() with a command of F_SETOWN and our PID as its argument. Figure 17.3. A server that processes urgent data Lines 1 “6: Load modules In addition to IO::Socket, we load the Fcntl module. This provides the definition for the F_SETOWN constant. Lines 7 “11: Install URG handler We install an anonymous subroutine, which tries to recv() 1 byte of urgent data on the socket using the idiom we gave earlier. If recv() is successful, we print an acknowledgment; otherwise, we get an error. Notice that even though we ask to receive 100 bytes of data, the protocol restrictions allow only 1 byte of urgent data to be delivered. This server will confirm that fact. Lines 12 “16: Create socket and accept() an incoming connection We create a listen socket and accept() a single incoming connection on it, storing the connected socket in $sock This is not a general-purpose server, so we don't bother with an accept() loop. Lines 17 “18: Set the owner of the socket We pass the connected socket to fcntl() , with a command of F_SETOWN and the current process ID stored in $$ as the argument. This sets the owner of the socket so that we receive the URG signal. Lines 19 “22: Read data from the socket We use sysread () to read conventional data from the socket until we reach the end of file. Everything we read is echoed to standard output. When we run the server and client together and interrupt the client twice, we see output like this: % urg_recv.pl Listening on port 2007... got 2 bytes of normal data: aa got 2 bytes of normal data: ab got 1 byte of OOB data! got 2 bytes of normal data: ac got 2 bytes of normal data: ad got 2 bytes of normal data: ae got 1 byte of OOB data! got 2 bytes of normal data: af ... Notice that the urgent data never appears in the normal data stream read by sysread() . As written, there is a potential race condition in this server. It is possible for urgent data to come in during or soon after the call to accept() , but before fcntl() has set the owner of the socket. In this case, the server misses the urgent data signal. This may or may not be an issue for your application. If it is, you could either -
engineer the client to introduce a brief delay after establishing the connection but before sending out urgent data; or -
apply fcntl() to the listening socket, in which case the owner setting is inherited by all connected sockets returned by accept() . The SO_OOBINLINE Option By default, TCP urgent data can be recovered only by calling recv() with the MSG_OOB flag. Internally, the operating system extracts and reserves incoming urgent data so that it doesn't mingle with the normal data stream. However, if you would prefer that the urgent data remain inline and appear amidst the normal data, you can use the SO_OOBLINE option. This option can be set with the IO::Socket sockopt() method or using the built-in setsockopt() function. Sockets with this option set return urgent data inline. The URG signal continues to be sent, but calling recv() with MSG_OOB can no longer be used to retrieve the urgent data and, in fact, will return an EINVAL error. The SO_OOBLINE option affects only the side of the connection that it is called on; it has no effect on how urgent data is handled at the remote end. Likewise, it affects only the way that incoming urgent data is handled, not the way it is sent. To demonstrate the effect of inlining on the server from Figure 17. 3, we can add the appropriate sockopt() call to the line beneath the call to accept(): $sock = $listen->accept; $sock->sockopt(SO_OOBINLINE,1); # enable inline urgent data Running the server and client together and generating a couple of interrupts now shows this pattern: % urg_recv2.pl Listening on port 2007... got 2 bytes of normal data: aa got 2 bytes of normal data: ab recv() error: Invalid argument got 1 bytes of normal data: ! got 2 bytes of normal data: ac got 2 bytes of normal data: ad got 2 bytes of normal data: ae recv() error: Invalid argument got 1 bytes of normal data: ! got 2 bytes of normal data: af Each time an urgent data byte is received, the server's URG handler is called, just as before. However, because the data is now inline, the recv() call fails with an error of EINVAL . The urgent data (an exclamation mark character) instead appears in the data stream read by sysread() . Notice that the urgent data always appears at the beginning of the data returned by a sysread() call. This is no coincidence . A feature of the urgent data API is that reads terminate at the urgent data pointer even if the caller requested more data. In the case of inline data, the next byte read by sysread() will be the urgent data itself. In the case of out-of- band data, the next byte read will be the character that follows the urgent data. Using select() with Urgent Data If you prefer not to intercept the URG signal, you can use either select() or poll() to detect the presence of urgent data. Urgent data appears as available "exception" data when using select() and as POLLPRI data when using poll() . Figure 17.4 shows another implementation of the urgent data server application using the IO::Select class. In this example, the server sets up two IO::Select objects, one for normal reads and the other for reading urgent data. It then selects between them using IO::Select->select() . If select() indicates that urgent data is available, we retrieve it using recv() . Otherwise, we read from the normal data stream using sysread() . Figure 17.4. An urgent data receiver implemented using select() Regrettably, this server needs to use a trick because of an idiosyncrasy of select() . Many implementations of select() continue to indicate that a socket has urgent data to read even after the program has called recv() , but calling recv() a second time fails with an EINVAL error because the urgent data buffer has already been emptied. This condition persists until at least 1 byte of normal data has been read from the socket and, unless handled properly, the program goes into a tight loop after receiving urgent data. To work around this problem, we manage a flag called $ok_to_read_oob . This flag is set every time we read normal data and cleared every time we read urgent data. At the top of the select() loop, we add the socket to the list to be monitored for urgent data if and only if the flag is true. From the user 's perspective, urg_recv3.pl behaves identically with urg_recv.pl . When we run it in one terminal and the urg_send.pl client in another, we see the following output when we press the interrupt key repeatedly in the client: % urg_recv3.pl Listening on port 2007... got 2 bytes of normal data: aa got 2 bytes of normal data: ab got 2 bytes of normal data: ac got 2 bytes of normal data: ad got 1 bytes of urgent data: ! got 2 bytes of normal data: ae got 1 bytes of urgent data: ! got 2 bytes of normal data: af ... |