Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

An Iostream-Compatible Socket Wrapper


December 2001/An Iostream-Compatible Socket Wrapper

An Iostream-Compatible Socket Wrapper

Maciej Sobczak

With suitable scaffolding, writing to a socket is as easy as cout << "Hello, world";.


Introduction

When I was attending the C++ course at my university (which followed the C course), I was told that now there are better tools to do the job than before, and that, for example, the following form should no longer be used:

fprintf(stdout, "Hello World!");

Instead, the object-based approach is preferred:

cout << "Hello World!" << endl;

I know a lot of people who would argue that the second line is not a bit more object-oriented than the first; the former is even better, because it clearly states what operation is performed on what entity. What is the improvement, then? The improvement comes from code reuse and the polymorphism that the iostream-compatible objects expose. Consider this:

void printHello(ostream &stream)
{
    stream << "Hello World!" << endl;
}

printHello is reusable, because it treats its arguments polymorphically [1]. You can use it with the standard cout object, or with any other stream you can invent, whether it is connected to a file (by std::ofstream) or to a string buffer (by std::ostringstream) or to something else derived from ostream. Indeed, with a slight change you can make it work with all of the standard streams, including the wide-character versions. Beyond that, it will also work with anything else that supports operator<< and understands endl, whether the stream happens to be derived from a standard stream class or not:

template<typename T>
void printHello(T &stream)
{
    stream << "Hello World!" << endl;
}

The possibilities are countless, and there is tons of code like the above to reuse. There is one minor problem, though. The standard library cannot provide all the classes needed for all I/O, because I/O programming is not limited to terminals, files, and memory devices like strings. Computers are used also for network communication where sockets are the popular abstraction. Is there any possibility to use the existing, iostream-compatible code in programs based on socket networking? Of course, there is.

Iostream Mechanics Revisited

Each iostream object (for example the standard cout) is composed of three parts:

  • The stream state component — this keeps information concerning the current condition of the stream.
  • The buffer component — this is a “transit” area for transmitted data.
  • The conversion component — this performs all the transformations between objects and raw sequences of bytes. The conversion component knows how to turn int or double into the sequence of bytes, for example.

Those three components are glued together by the tools available in the language: inheritance, aggregation, and containment. For example, Figure 1 shows the structure of ostream objects (only the key components are shown). Above the horizontal dashed line, there are framework classes, which provide the interface of the stream. Below that line, two concrete classes are shown, which together implement the ultimate ostream object (file-based stream in this example). The istream objects have conceptually the same structure, where the basic_istream class is on the bottom of the hierarchy instead of basic_ostream.

These classes are in fact templates. This was introduced a few years ago by the Standard and distinguishes “new” from “old” iostreams [2]. The charT template parameter represents the character type used to transmit the data. When the term “ostream” is used, it means the basic_ostream<charT> instantiated with the char type. In fact, ostream is merely a typedef for basic_ostream<char>; istream and iostream are typedefed in a similar way. There is also a second template parameter for this template, called traits, but its default value is good enough for most needs. (It is good enough to be used in the istream and ostream typedefs, for example.)

The ios_base and basic_ios classes make up the state component of the ostream object.

The basic_streambuf object is responsible for providing the uniform view over the physical I/O device, which means that it provides the access to the underlying device through its public methods. basic_ostream objects keep the pointer to the basic_streambuf components and use its methods when they need to write or read some bytes as objects of type charT.

The streambuf component manages its own buffer area. In order to present the abstract notion of unlimited-size buffer, streambuf has to properly react when the overflow (when the user puts more bytes than fit into the buffer — this can happen in ostream objects) or underflow (when the user wants to extract more bytes than are currently in memory — in istream objects) condition occurs.

For example, for file I/O, when overflow occurs while performing the insertion operation (most frequently by operator<<), the ofstream object has to flush some or all of its buffer to the file, making room in the buffer area for new bytes. Similarly, when underflow happens, the ifstream object has to read a new portion of data from the file into the buffer area, making them available for future extraction operations (usually by operator>>).

Writing a new stream buffer class that will work with a new I/O device (for example network sockets) concentrates on those two unusual conditions. For socket-based I/O, the overflow condition should trigger writing the buffer’s contents to the socket, and for the underflow condition, the new portion of data should be read from the network connection.

The Simple Socket Wrapper

Before implementing the full solution in the context of iostream classes, I will present a simple wrapper for the socket functionality, which can be used on its own without streams. The iostream classes will then be implemented in terms of this wrapper.

The wrapper defines two of its own exception classes for error reporting:

class SocketLogicException :
    public std::logic_error
{
    // ...
};
class SocketRunTimeException :
    public std::runtime_error
{
    // ...
};

The first class is used to report design errors. For example, when the programmer wants to read from a socket object that was not connected, it is a serious design flaw. The instances of the second class are thrown when the error related to the network happened. For example, if the IP address cannot be resolved, it can mean that the network is unavailable. The what method in both classes gives a human-readable message describing the error.

Listing 1 presents the interface of the socket wrapper class, TCPSocketWrapper. This class can be used only for TCP sockets (stream mode), opened in blocking mode. In fact, the user does not have any control over the internal socket details, but this is not a restriction for the purpose of writing an iostream-compatible class.

Listing 2 presents the simple server program that uses TCPSocketWrapper. It waits for a connection, prints the message read from the socket, then closes its end, and waits for another connection. Listing 3 presents a simple example client program that connects to the server process running on the same machine, sends the message to the server, and quits [3]. (Note: both programs are illustrative examples; production versions would be more complex and perform robust error checking and handling.)

The Iostream-Compatible Socket Wrapper

A basic understanding of iostream mechanics is enough to implement a custom iostream-compatible class. The key component is the stream buffer object, and for custom streams, you need to write your own buffer class, derived (possibly indirectly) from the basic_streambuf template. Listing 4 presents the skeleton of the custom, stream buffer class. To be fully compatible with the “new” iostreams, TCPStreamBuffer is a template, which accepts two template parameters, as does basic_streambuf. Both parameters are used to instantiate the base class basic_streambuf.

The buffer object keeps a reference to the socket wrapper. It will use this wrapper to perform all the physical I/O operations. It is possible to declare that the buffer object should be responsible for the socket wrapper. The second argument to the buffer’s constructor is a boolean flag. If it is true, the buffer object will automatically close the socket in its own destructor. The third parameter of the constructor is the size of the buffer area that will be allocated if the user did not provide his own buffer. You can experiment with different default values to obtain the best performance. The buffer’s size has an influence on how often it should be flushed (or reloaded) and, thus, how often the physical I/O is performed.

Note the overflow and underflow methods in TCPStreamBuffer. They are virtual methods (they are declared so in the basic_streambuf base class) and will be called when there is a need for flushing or reloading the buffer area of the iostream object’s stream buffer component. The core part of the underflow method looks like this (for details please refer to the complete source code available for download at <www.cuj.com/code>):

int readn = rsocket_.read(inbuf_,
    bufsize_ * sizeof(char_type));
if (readn == 0)
    return (int_type)traits::eof();
setg(inbuf_, inbuf_,
    inbuf_ + readn / sizeof(char_type));
return sgetc();

Here, the socket wrapper is used to read the next data packet from the network. The number of bytes to read is the buffer size expressed in bytes, not in the units used to instantiate the template, hence the scaling. After reading the data, underflow makes a call to setg to reset the basic_streambuf internal pointers to the buffer area.

Note the return value from the underflow method: it is the next character from the buffer area, taken by the call to basic_streambuf::sgetc. If the underflow function cannot successfully fill the buffer area, it should return the special end-of-file value.

The overflow method is written in essentially similar style.

You can use the stream buffer class to build a fully-functional stream class. Listing 5 shows the essence of the socket-based stream. Using containment, it manages the stream buffer class (a TCPStreamBuffer object) and instantiates the base class basic_iostream with the pointer to the stream buffer. Private inheritance is used to ensure that the TCPStreamBuffer object is constructed before passing its pointer (by implicitly upcasting the this pointer) to the basic_iostream component. The layout of the whole stream object is still compatible with Figure 1, except for the fact that the stream buffer object is physically contained by the most-derived class, TCPGenericStream, as an instance of its private base class. Note, that TCPGenericStream is still a template.

The constructor of TCPGenericStream accepts the socket wrapper object. This means that, before constructing the stream object (and finally reusing all that iostream-based code), the socket wrapper has to be created. These semantics are useful for server-side code; on the server-side, the usual steps of setting up the connection are:

  • Create the listening socket, which will accept connections from clients. (These steps are performed with the bind and listen functions that are part of the sockets API.)
  • Create the connected socket when the client opens the connection. (This is achieved by calling the accept function.)

After these two steps, the server-side process has an open socket through which it can communicate with the client. TCPGenericStreams semantics make it very easy and intuitive. Its constructor takes that open socket and turns it into stream object.

On the client-side, however, the steps taken to set up the communication are much simpler:

  • Create the socket.
  • Connect it to the server (by calling the connect function).

Here, there is always just one socket. It could be more intuitive not to split the responsibilities between two different objects: one socket wrapper (for making the connection) and one stream built around the socket (for inserting and extracting data). TCPGenericClientStream is designed exactly to combine those two tasks. Listing 6 presents its complete implementation. Again, the private inheritance ensures that TCPSocketWrapper is created before passing its reference to TCPGenericStream’s constructor.

TCPGenericClientStream’s constructor takes two arguments: the IP address of the server to which it should connect (as either a raw IP address "xxx.xxx.xxx.xxx", or as a symbolic host name "comp.company.com") and its IP port number. In the constructor’s body, the private socket wrapper connects to the server. Note that the base class, TCPGenericStream, is instantiated with the socket wrapper contained in the TCPGenericClientStream object. No other socket object is necessary to set up the communication on the client side.

All the classes are templates in the spirit of the iostream library. To make life easier, you can use typedefs similar to the ones found in the standard library for the istream and ostream classes:

typedef
TCPGenericStream<char>
TCPStream;

typedef
TCPGenericStream<wchar_t>
TCPWStream;

typedef
TCPGenericClientStream<char>
TCPClientStream;

typedef
TCPGenericClientStream<wchar_t>
TCPWClientStream;

Sample Application

Listing 7 shows a sample server application. This application opens the listening socket and waits for a connection from the client; once established, it reads the commands sent by the client. The command is just an integer value:

  1. The server sends back a string message.
  2. The server sends back an integer value.
  3. The server sends back a complex number.

For any other command, the server just quits. Of course, a normal full-blown server would fork or at least spawn a thread for each client, but here it is not needed.

These different commands show that the formatting capabilities of the stream object are preserved, even for new, custom stream classes. If you can write this:

complex<double> cplx(1, 2);
cout << cplx << endl;

you can also write this:

complex<double> cplx(1, 2);
yourstream << cplx << endl;

This is the beauty of the iostream class hierarchy.

Listing 8 presents the example client program. It allows the user to choose which command should be sent to the server, sends it, and (in case of commands 1, 2, and 3) reads the response from the server. Again, the formatting abilities of stream classes are preserved because the TCPStream and TCPClientStream classes have appropriate base classes. The server can insert whatever it wants to its stream, and if the client knows how to extract it properly, it can do it with a simple call to the extraction operator. Of course, this applies not only to standard classes (like string and complex), but also to any user-defined class with operator<< and operator>> overloaded for iostreams.

Acknowledgements

I would like to thank James Dennett for his tips on porting the code to Linux (although it was not tested on this platform) and to Marta Jakubowska for her support while I was working on this article.

Special thanks goes to Herb Sutter for his instructive comments on the initial draft.

Notes and References

[1] In fact, this kind of overloading is to some extent possible in C. For example, POSIX read and write functions accept the file descriptor, but from the programmer’s point of view, it does not matter if the descriptor denotes file, pipe, socket, or anything else. This kind of “polymorphism” is present in many places in different operating systems, which confirms that OO is a way of programming rather than any language construct typical to C++ or Java.

[2] It also distinguishes “new” compilers from “old” ones if we take into account that the compiler and library are usually shipped together. I have written the code and successfully tested it on Microsoft Visual C++ 7.0 beta. You may have problems running the examples if you compile them with Microsoft Visual C++ 6.0 — this is the result of a bug in the library shipped with this compiler. See <http://support.microsoft.com/support/kb/articles/Q240/0/15.ASP> to learn about the bug and how to fix it. You may also need some recent version of g++ if you want to use the code on Linux.

[3] Note the use of socketsInit and socketsEnd. These functions are required on the Microsoft Windows system to properly initialize the sockets library (when compiling the code on Microsoft Windows, be sure to link with Ws2_32.lib). This is not required on Unix-like systems, but for compatibility it is a good idea to use them. On Unix systems, these functions do nothing.

[4] Angelika Langer and Klaus Kreft. Standard C++ IOStreams and Locales: Advanced Programmer’s Guide and Reference (Addison-Wesley, 2000).

[5] Cameron Hughes and Tracey Hughes. Mastering the Standard C++ Classes (John Wiley & Sons, 1999).

[6] “ISO/IEC 14882:1998(E), Programming Languages — C++.” A very cryptic name for the C++ Standard, it is the final reference in case (and also source) of confusion.

Maciej Sobczak is a student at the Institute of Computer Science, Warsaw University of Technology. He is passionate about C++ (and experiments with other technologies, too) with two years working experience. Maciej is interested in distributed, object-oriented computing. You can visit him at < www.maciejsobczak.com>.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.