Windows Socket Streams

Windows Socket Streams #

Introduction #

The code described in this article implements traditional C++ streams for communication over sockets. It is based on socket++, a library originally developed by Gnanasekaran Swaminathan and caries the following copyright notice:

Copyright (C) 1992,1993,1994 Gnanasekaran Swaminathan Permission is granted to use at your own risk and distribute this software in source and binary forms provided the above copyright notice and this paragraph are preserved on all copies. This software is provided as is with no express or implied warranty.

It has however undergone massive changes and upgrades over the years.

The original socket++ code was dealing only with Unix sockets which are fairly different beasts compared to their Windows counterparts. In this sense my code is not portable and it can be used only on Windows sockets.

Many functions return an error code object as a result. If you don’t know what these hybrids between exceptions and error codes are, see the article on this subject.

The example code shown is this article uses the standard TCP services daytime and echo. To make them work you have to enable these services in the Windows Features dialog box:

Low Level Objects #

At the lowest level there are object encapsulations for sockets and addresses.

sock object #

The sock object is an encapsulation of a Winsock SOCKET handle. It can be used both for stream (TCP) or datagram (UDP) sockets. Its member function encapsulate most Winsock functions. The list is long and it’s better to look at the code or the Doxygen documentation. Here I wanted to point to a few details.

To be an well-behaved C++ object sock objects needed a copy constructor and an assignment operator. Meanwhile socket handles cannot be duplicated using DuplicateHandle functions. The solution is to use a “lives” counter that keeps track of how many “lives” a SOCKET handle has. The handle is closed (using CloseHandle() function) only when the lives count reaches 0. The solution is similar to the one used by shared_ptr objects but back in the day when this code was written there were no shared_ptr objects in C++.

If you find the sock object is missing a wrapper for some function you desperately need in your app, don’t worry: there is SOCKET conversion operator that allows you to use a sock anywhere a SOCKET can be used.

inaddr object #

The inaddr object encapsulates a sockaddr_in structure. Note that our inaddr object concerns itself only with Internet IPV4 addresses. Just like in the case of sock object, inaddr objects have a sockaddr conversion operator that allows you to use it in any place where you would use a `sockaddr.

The member functions of inaddr take care of host-to-network conversions as well as eventual DNS lookup.

Examples #

Here is an example of a TCP connection:

  sock a (SOCK_STREAM);
  inaddr him ("google.com", 80);
  a.connect (him);

Receiving some data from a socket using the daytime protocol:

  char buf[256];
  sock a (SOCK_STREAM);
  a.connect (inaddr ("localhost", 13));
  int len = a.recv (buf, sizeof (buf));
  if (len > 0)
  {
    buf[len] = 0;
    printf ("Date is: %s", buf);
  }

Sending (and receiving) through a socket using the echo protocol:

  const char *fox = "The quick brown fox jumps over the lazy dog\n";
  char buf[256];
  sock a (SOCK_STREAM);
  a.connect (inaddr ("localhost", 7));
  a.send (fox, strlen(fox));
  int len = a.recv (buf, sizeof (buf));
  if (len > 0)
  {
    buf[len] = 0;
    printf ("Echo: %s", buf);
  }

Socket Streams #

Now that we’ve seen the lower level entities, let’s move up to socket streams. Here the main piece is the sockbuf object. This is derived from both std::streambuf and sock classes. As such, it inherits all the sock member functions (connect, bind, recv, send, etc.). It also implements the virtual protected functions required by the std::streambuf interface (underflow, overflow, showmanyc, etc.).

If you just plan to use the socket streams, you shouldn’t concern too much with the implementation details of this class. It suffice to say that it maintains two separate buffers - one for reading and one writing. Buffer size can be changed and even, if need be, users can specify their own buffer.

The actual socket streams are isockstream, osockstream and sockstream. They are specializations of the template class generic_sockstream<strm> with std:istream, std::ostream and respectively std:iostream as a template argument.

Examples #

Let’s rewrite the previous examples using our socket streams.

The daytime example could be written as:

  isockstream is (inaddr ("localhost", 13));
  string s;
  getline (is, s);
  cout << "Date is: " << s << endl;

Here is another way of writing the echo example:

  char *fox = "The quick brown fox jumps over the lazy dog";
  sockstream ss (inaddr ("localhost", 7));
  ss << fox << endl;

  char c;
  cout << "Received: ";
  do
  {
    ss >> noskipws >> c;
    cout << c;
  } while (c != '\n');

Programming with socket streams #

Socket streams behave just like standard file streams and you should have little trouble using them. They have a “pointer to” operator:

  sockbuf* operator ->()

that returns the associated sockbuf object. As sockbuf is derived from socket, this is a handy way to access all the underlining socket functions. For instance if you want to shutdown one end of the socket associated with a stream you can just write:

  sockstream ws;
  //....    
  ws->shutdown (sock::shut_write);

Keep in mind that output data is buffered; use flush() or endl to really send the data out.

History #

01-Dec-2019 Initial version