"Out-of- Band " Data and the Urgent Pointer Remember the send() and recv() calls from Chapter 4 (Other Socket-Related Functions)? We are now going to use them for the first time. You can send a single byte of TCP urgent data over a connected socket by calling send() with the MSG_OOB flag: send ($socket,'a',MSG_OOB) or die "Can't send(): $!"; In this code fragment, we call send() to transmit the character "a" across the socket $socket . The MSG_OOB flag specifies that the message is urgent and must be delivered right away. On the other end, the recipient of the message can read the urgent data by calling recv() with the same flag: recv ($socket,$data,1,MSG_OOB) or die "Can't recv(): $!"; Here we're asking recv() to fetch 1 byte of urgent data from the socket and store it in the scalar $data . This looks simple enough, but there is significant complexity lurking under the surface. Although the term "out-of-band data" implies that it is transmitted outside the normal data stream, this is not the case. Urgent data works in the manner illustrated in Figure 17.1. During normal TCP operations, the sending process queues data into the operating system's TCP transmit buffer. The contents of the buffer are spooled across the network and eventually end up in the TCP receive buffer of the destination host. A sending process now sends 1 byte of urgent data by calling send() with the MSG_OOB flag. This causes three things to happen: Figure 17.1. Sending TCP urgent data -
The TCP stream is put into URGENT mode, and the operating system alters the receiving process to this fact by sending it the URG signal. -
The urgent data is appended to the transmit buffer, where it will be sent to the receiving process using the normal TCP flow-control rules. -
A mark, known as the "urgent pointer," is added to the TCP stream to mark the position of the urgent data. There is only one urgent pointer per TCP stream. When the receiving process calls recv() with the MSG_OOB flag, the operating system uses the urgent pointer to extract the urgent data byte from the stream and return it separately from the rest. Other calls to sysread () and recv() ordinarily skip over the urgent data, pretending to the caller that it isn't there. Because of the way TCP urgent data works, there are numerous caveats and restrictions on its use: -
Only a single byte of urgent data can be sent at one time. If you use send() to send multiple characters , only the last one will be considered urgent by the receiver. -
Because there is only one urgent pointer per stream, if the sender calls send() to write urgent data multiple times before the receiver calls recv() , only the last urgent event will be received. All earlier urgent data marks will be erased, and the earlier urgent data bytes will appear in the normal data stream. -
Although the receiving process is sent the URG signal immediately, the urgent data itself is subject to all the TCP flow-control rules. This means that the receiving process may be notified that there is urgent data available before it has actually arrived. Furthermore, it may be necessary to clear some room in the TCP receive buffer before the urgent data byte can be received. Caveat 3 is the real kicker . If the receiver calls recv() with MSG_OOB before the urgent data has arrived, the call will fail with an EWOULDBLOCK error. The alternatives are to just ignore the urgent data or to perform one or more normal reads until the urgent data arrives. The work needed to implement the latter option is eased slightly by the fact that sysread() stops automatically at the urgent pointer boundary. We'll see examples of this later. |