Adding socket support to the iostreams hierarchy

Written by Gordon Zeglinski

Introduction
As Caesar was warned "Beware the ides of March," so too should programmers be warned "Beware the quirks of compilers." Compared to C-Set++ and Borland, Watcom has some strangeness. Watcom uses "POSIX" file handles in order to open more than the default number of files, _grow_handles must be called, which is classified as a Watcom specific function. Interestingly enough, there is no reference to this function in any other file I/O related function. Most annoying to say the least.

C++ iostreams provide a simple highly flexible method of performing formatted and unformatted I/O. The basic implementation provides classes to perform I/O on files, memory buffers, and the standard streams (error, input, output). Any good C++ book will tell you how to format output, or write raw data to a stream. Few books will detail how to extend the hierarchy.

In this column, we will examine adding socket support to the iostreams hierarchy. There's no point reinventing the wheel, so we'll start by looking at socket++, a Unix-based socket extension to iostream.

Socket++
The original Unix version has classes to support inet sockets and pipes. In addition, the FTP and SMTP protocols are encapsulated in classes. Remember that BSD sockets can support sockets that aren't Internet based but OS/2 only supports Internet (Inet) sockets. We will only look at this library's generic socket and Inet socket support.

All reading and writing to the medium is performed by the streambuf hierarchy. The generic socket buffer object, sockbuf, is the heart of library. We'll look closer at stream buffers later. That being said, the programmer almost never works directly with the stream buffer. Instead, the stream hierarchy is used to read/write data. The stream hierarchy provides operators and functions to perform formatted and unformatted I/O on a buffer. Three classes are derived to provide input only, output only and input/output streams. These classes are isockstream, osockstream, and iosockstream. These last 3 classes are very simple. As shown below, isockstream defines only a few simple member functions. All of the work functions are inherited from ios and istream. Figure 1: Definition of the isockstream class.

Definitions of the other 2 classes are similar (see sockstream.h).

Internet sockets use the buffer class sockinetbuf. This class is derived from sockbuf, and provides tcp/ip specific functions. The classes isockinet, osockinet and iosockinet are derived from their generic socket counter-parts.

The sockbuf class performs all socket operations, binding, connecting, listening, etc. A stream buffer (streambuf) class provides buffers for buffering input and output. Utilizing the unbuffered I/O capabilities doesn't require any additional programming effort, so we will look at the more complex case of buffered I/O.

When a stream needs to write data, it calls member functions in the streambuf class to write the data into the buffer. When the buffer becomes full, the streambuf::overflow is called. The overflow function must move the data from the output buffer (also called the put area) to the output medium (in this case the socket).

When a stream reads from streambuf, it calls streambuf member functions to remove data from the buffer, if the buffer is empty or doesn't contain enough data, streambuf::underflow is called. The underflow function reads data from the input medium and places it in the input buffer (also called the get area).

The get and put areas are sections of the reserve area. The reserve area is the block of memory which is used for buffering I/O. Both underflow and overflow are virtual member functions of streambuf. At the very least, a custom buffer class must declare members for overflow and underflow. Note: some implementations of the iostream classes, have overflow and underflow as pure virtual classes. The following is a modified version of sockbuf. Figure 2: Definition of the modified sockbuf class.

As mentioned before, the sockbuf class defines the member functions underflow, and overflow. In addition, it defines the member function doallocate. The virtual function doallocate allows an ancestor of streambuf to customize the allocation of the internal buffers. Figure 3: sockbuf::doallocate implementation.

The '0' parameter in the call to setb indicates that streambuf is not to free reserve area.

In the sockbuf definition, I added a destroy function. Below is the function bodies for the destructor and the assignment operator. Note that in the original version of the assignment operator, the destructor was explicitly called. This resulted in hidden destructor code being called that erased the calling instance's virtual function tables. The Destroy function contains all the programmer written clean up code. It can be called by either the destructor or the assignment operator without strange side effects. Figure 4: sockbuf::Destroy implementation.

Porting the Unix socket code to OS/2 wasn't overly difficult. It's simply a matter of removing Unix-like headers and using the appropriate ANSI ones. To save a bit of work, the BSD version of select was used instead of the OS/2 version.

Wrapping Things Up
Extending the iostream class is simply a matter of creating the appropriate buffer class. In the simplest case, the new buffer class must overload the underflow and overflow streambuf functions. Once the buffer class has been defined, a new stream class is created. The stream class is derived from either istream, ostream of iostream. That's all that needs to be done.

C++