Tuesday, October 27, 2009

Using TCP Urgent Data
























Network
Programming with Perl
By
Lincoln D. Stein
Slots : 1
Table
of Contents
Chapter 17.  TCP Urgent
Data






    Content







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 class=docEmphasis>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, class=docEmphasis>urg_recv3.pl behaves identically with
urg_recv.pl. When we run it in
one terminal and the class=docEmphasis>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
...








       




    Top

    No comments: