Urgent Data

A while back I stressed the point that TCP sockets provide an ordered stream of data. In other words, you can imagine the TCP data stream as a queue. For example, one end of a socket connection writes some data to the connection, this pushes it onto the queue. It moves through the various stages (local buffers, network transit, remote buffers), and then is popped off this ‘queue’ by the receiving socket.

This mental model holds true for typical TCP communication. TCP urgent data, more often referred to as out-of-band data, allows you to push data all the way to the front of the queue, where it can be received by the other end of the connection as soon as possible, even bypassing data that is already en route.

There’s a method on Socket that we haven’t come across yet, Socket#send.

Socket#send is like a specialized version of Socket#write (inherited from IO). In fact, without arguments, it behaves just like `write'.

# These have the same effect
socket.write 'foo'
socket.send 'foo'

Whereas the write method is generalized to be used with any IO object, the send method is specialized to work just with sockets. This specialization allows Socket#send to accept a second argument: flags. We can specify a flag to send to denote some data as urgent.

Sending Urgent Data

Let’s have a look:

require 'socket'

socket = TCPSocket.new 'localhost', 4481

# Send some data using the standard method
socket.write 'first'
socket.write 'second'

# Send some urgent data
socket.send '!', Socket::MSG_OOB

To send a byte of urgent data, we call send and pass the Socket::MSG_OOB constant as the flag. OOB here refers to out-of-band.

This is the what it takes to send some urgent data, but this isn’t enough to cause the receiver to get the urgent data first. In other words, the sender and receiver need to collaborate in order for this to work.

Receiving Urgent Data

Here’s how the receiver might get the urgent data using Socket#recv.

require 'socket'

Socket.tcp_server_loop(4481) do |connection|
  # receive urgent data first
  urgent_data = connection.recv(1, Socket::MSG_OOB)

  data = connection.readpartial(1024)
end

In order to receive urgent data, we had to use Socket#recv with the same flag we used to send the urgent data. Just as Socket#send is a socket-specific way to write data, Socket#recv is a socket-specific way to read data. It, too, can accept flags.

Notice that we were able to consume the urgent data before the ‘regular’ data, even though the regular data was written first. This is what you can do with urgent data. Notice also that we had to explicitly receive the urgent data. If we hadn’t called recv, this server wouldn’t have noticed the urgent data. In other words, if the receiver isn’t looking for urgent data, it won’t receive any. Read on for ways of dealing with this.

Calling connection.recv(1, Socket::MSG_OOB) will fail with Errno::EINVAL if there’s no pending urgent data.

Limits

You may have noticed that I only sent a single byte of urgent data in the above example. This was intentional. The TCP implementation has limited support for urgent data in that only a single byte of urgent data can be sent at one time. If you send multiple bytes of urgent data, only the last byte will be considered urgent. Any earlier bytes will appear as part of the ‘regular’ TCP data stream.

Urgent Data and IO.select

As with most things, we can use IO.select to monitor sockets for urgent data. However, there is a serious caveat.

Remember how I said that the third argument to IO.select was an array of IO objects for which you are interested in out-of-band data? Here’s how it might be used:

for_reading = [<TCPSocket>, <TCPSocket>, <TCPSocket>]
for_writing = [<TCPSocket>, <TCPSocket>, <TCPSocket>]

IO.select(for_reading, for_writing, for_reading)

Notice that we’re passing the array of sockets to monitor for reading as the third argument? This means that if any of those sockets receives urgent data, they’ll be included in the third element of the returned Array from IO.select.

This is great, it means we can monitor sockets for urgent data without having to call recv blindly. However, in my experience, IO.select will continue to say that there’s urgent data available even after it’s all been consumed! This continues until some of the ‘regular’ TCP data stream has been consumed, most likely until the local recv buffer is empty. This means that you’ll need to add some extra error handling or state tracking to make sure you don’t get stuck in a tight loop trying to consume urgent data that isn’t coming.

Given this caveat, and the single byte limitation, this is a rarely-used TCP feature.

The SO_OOBINLINE Option

Another way to deal with urgent data is just to stick it in the regular data stream. There is a socket option, called SO_OOBINLINE, that will allow out-of-band data to be received in-band. In other words, the urgent data will be combined, in order, with the ‘regular’ data stream. With this option enabled, the urgent data will no longer be treated as such. It will be read from the queue in the same order it was sent relative to other writes.

Here’s how to turn it on:

require 'socket'

Socket.tcp_server_loop(4481) do |connection|
  # receive urgent data inline with 'regular' data
  connection.setsockopt :SOCKET, :OOBINLINE, true

  # note that the read stops when it 
  # encounters urgent data
  connection.readpartial(1024) #=> 'foo'
  connection.readpartial(1024) #=> '!'
end

In this example, the foo data is received before the ! byte, so the urgent data is no longer received first. I made a point of showing that the read family of methods is aware of urgent data. Even though it could have fit the foo data and the ! data inside its 1024 byte limit, it stopped reading when it encountered urgent data, returned what it had, then started over.

This option only has an effect on the receiving socket, not the sending socket.