Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Windows Sockets Streams

4.33/5 (3 votes)
2 Dec 2019MIT4 min read 10.2K   469  
C++ streams for socket communications in Windows

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 carries 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 my previous article on this subject.

The example code shown in 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:

Image 1

Low Level Objects

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

Socket 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 a 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 Objects

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:

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

Receiving some data from a socket using the daytime protocol:

C++
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:

C++
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 suffices to say that it maintains two separate buffers - one for reading and one writing, Buffer size can be changed and even, if need be, user 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:

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

Here is another way of writing the echo example:

C++
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:

C++
sockbuf* operator ->()

that returns the associated sockbuf object. If you remember, sockbuf is derived from socket so 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:

C++
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.

The End, For Now...

If you liked my socket streams, stay tuned for the next article where I'll show you how to use them to build a multi-threaded TCP server and a small HTTP server that can be easily embedded in your programs.

History

  • 1st December, 2019: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License