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