Networking - C++ Recipes: A Problem-Solution Approach (2015)

C++ Recipes: A Problem-Solution Approach (2015)

CHAPTER 12

image

Networking

Communicating over the Internet is becoming an increasingly integral part of many modern computer programs. It’s hard to find any programs that don’t connect to another instance of the same program or to a web server that provides essential functionality to some part of the program or an app. This creates opportunities for developers to specialize in the field of network programming. You can take several different approaches when writing connected programs, and using a high-level library is a valid technique; however, this chapter looks at the Berkeley Sockets library that can be used on OS X, Linux, and Windows.

Berkeley Sockets first appeared in 1983 in the Unix operating system. That OS became unencumbered by copyright issues in the late 1980s, allowing the Berkeley Sockets API to become the standard implementation used on most OSs today. Even though Windows doesn’t support Berkeley directly, its network API is almost entirely the same as the Berkeley standard API.

This chapter covers how to create and use sockets to produce programs that can communicate with each other over a network such as the Internet. Recipes 14-1, 14-2, and 14-3 cover the same material for each of the major OSs in use today. You should read the Recipe relevant to the system you’re using for development then move on to Recipe 12-4.

12-1. Setting Up a Berkeley Sockets Application on OS X

Problem

You would like to create a network socket program that can be used on OS X.

Solution

OS X supplies the Berkeley Sockets API as part of the OS and can be used without having to resort to external libraries.

How It Works

Apple provides the Xcode IDE, which you can use to build OS X applications from an Apple computer. Xcode is freely available from the App Store. Once installed, you can use Xcode to create programs to be run on computers of your choosing. This recipe creates a command-line program that connects to the Internet and opens a socket to a server.

To begin, you have to create a valid project for your application. Open Xcode, and select the Create a New Xcode Project option shown in Figure 12-1.

9781484201589_Fig12-01.jpg

Figure 12-1. The Xcode Welcome screen with the Create a New Xcode Project option

You’re asked to select the type of application you wish to create. Select the Command Line Tool option under the OS X Application category; Figure 12-2 shows this window.

9781484201589_Fig12-02.jpg

Figure 12-2. The OS X Application Command Line Tool option

Next, you’re asked to nominate a folder in which to store your projects files. After you do, the main Xcode window opens, and you can select your source files from the Project View on the left. Replace the code in the new CPP file with the code from Listing 12-1 to create an application that opens a socket to the Google HTTP web server.

Listing 12-1. Opening a Berkeley Socket

#include <iostream>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

using SOCKET = int;

using namespace std;

int main(int argc, const char * argv[])
{
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

addrinfo *servinfo{};
getaddrinfo("www.google.com", "80", &hints, &servinfo);

SOCKET sockfd{
socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)
};

int connectionResult{ connect(sockfd, servinfo->ai_addr, servinfo->ai_addrlen) };
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}

freeaddrinfo(servinfo);

return 0;
}

The code in Listing 12-1 requires a short primer on how the Internet works in order for you to fully understand what is happening. Before you can connect to a server, you need to know the address at which it’s located. This is best found using the domain name service (DNS). DNS works by keeping a cache of the server addresses for a given host name. In this example, you’re asking DNS for the address associated with www.google.com. If you’re creating a program to run on your own network, you can specify the IP addresses of the servers manually, but this usually isn’t possible for programs that access information using the Internet. Servers can be moved, and IP addresses can be changed or reused for different systems at different times. The getaddrinfo function asks DNS for the address associated with www.google.com on port 80.

Server addresses for specific services usually consist of two parts: the IP address of the computer to connect to and the port of the specific service on that server that you wish to communicate with. The World Wide Web communicates using the HTTP protocol, which is commonly configured to serve data using port 80. You can see in Listing 12-1 that this is the port with which you try to establish a connection on the remote computer.

The getaddrinfo function takes the web address, the port, and two addrinfo structs as parameters. These first of these structs provides the DNS service with some hints as to the type of connection you want to establish with the remote computer. The two most important at this point are the ai_family and ai_socktype fields.

The ai_family field specifies the type of address you would like to retrieve for your program. This allows you to specify whether you want an IPv4, IPv6, NetBIOS, Infrared, or Bluetooth address. The option supplied in Listing 12-1 is unspecified, which allows the getaddrinfofunction to return all the valid IP addresses for the requested web address. These valid IP addresses are represented by the same addrinfo struct and are passed back to the program through the pointer supplied to getaddrinfo’s fourth parameter.

The ai_socktype field lets you specify the type of transmission mechanism to be used with the socket in question. The SOCK_STREAM option in Listing 12-1 creates a socket that uses TCP/IP as the transport mechanism. This type of socket allows you to send packets of information that are guaranteed to arrive in order at the destination. The only other type of transmission mechanism used in this chapter is the SOCK_DGRAM type. This transport mechanism doesn’t guarantee that packets will arrive or that they will arrive in the order expected; however, they don’t have the same overheads that come with the TCP/IP mechanism and therefore can result in packets being sent with much lower latency between computers.

The servinfo returned by the getaddrinfo function can be used to create a socket. A socket file descriptor is obtained from the socket function, which is passed the info from the servinfo structure. The servinfo structure could be a linked list in this instance, because Google supports both the IPv4 and IPv6 address formats. You could write code here that chooses the address to use and acts appropriately. The ai_next field stores a pointer to the next element in the list for as long as the list has more elements. The ai_family, ai_socktype, andai_protocol variables are all passed into the socket function to create a valid socket to use. You can call the connect function once you have a valid socket. The connect function takes the socket ID, the ai_addr field from the servinfo object containing the address, andai_addrlen to determine the length of the address. If the connection wasn’t obtained successfully, you receive a return value of -1 from connect. Listing 12-1 demonstrates this by printing whether the connection was successful.

12-2. Setting Up a Berkeley Sockets Application in Eclipse on Ubuntu

Problem

You would like to create a network socket program that can be used on Ubuntu using Eclipse.

Solution

Ubuntu supplies the Berkeley Sockets API as part of the OS and can be used without having to resort to external libraries.

How It Works

The Eclipse IDE can be used to build applications on a computer running Linux. Eclipse is freely available from the Ubuntu Software Center. Once installed, you can use Eclipse to create programs to be run on computers of your choosing. This recipe creates a command-line program that connects to the Internet and opens a socket to a server.

To begin, you have to create a valid project for your application. Open Eclipse, and select the Project image New option from the menu bar. The New Project Wizard opens, as shown in Figure 12-3.

9781484201589_Fig12-03.jpg

Figure 12-3. The Eclipse New Project Wizard

The New Project Wizard allows you to select C++ Project as an option. Then, click Next, and you’re presented with the C++ Project settings window shown in Figure 12-4.

9781484201589_Fig12-04.jpg

Figure 12-4. The Eclipse C++ Project settings window

In this window, you can name your project and decide which folder it should be created in. Under Project Type, select Executable image Hello World C++ Project. Doing so creates a project that’s configured to be built as an executable and that contains a source file for adding your own code.

The sample code in this chapter uses features from the C++11 language specification. A default Eclipse project doesn’t have this enabled. You can turn it on by right-clicking your project and selecting Properties. You should see the Settings window shown in Figure 12-5, with categories on the left side. To enable C++11 support, select Settings under C/C++ Build, adding –std=c++11 to the All Options field, and click OK.

9781484201589_Fig12-05.jpg

Figure 12-5. Adding C++11 support to your Eclipse project

Replace the code in the new CPP file with the code from Listing 12-2 to create an application that opens a socket to the Google HTTP web server.

Image Note The code and description that follow are exactly the same as in Recipe 12-1. If you’ve already read this material, you may wish to skip to Recipe 12-4. If you skipped Recipe 12-1 because OS X isn’t relevant to you, then read on.

Listing 12-2. Opening a Berkeley Socket

#include <iostream>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

using SOCKET = int;

using namespace std;

int main(int argc, const char * argv[])
{
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

addrinfo *servinfo{};
getaddrinfo("www.google.com", "80", &hints, &servinfo);

SOCKET sockfd{
socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)
};

int connectionResult{ connect(sockfd, servinfo->ai_addr, servinfo->ai_addrlen) };
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}

freeaddrinfo(servinfo);

return 0;
}

The code in Listing 12-2 requires a short primer on how the Internet works in order for you to fully understand what is happening. Before you can connect to a server, you need to know the address at which it’s located. This is best found using the domain name service (DNS). DNS works by keeping a cache of the server addresses for a given host name. In this example, you’re asking DNS for the address associated with www.google.com. If you’re creating a program to run on your own network, you can specify the IP addresses of the servers manually, but this usually isn’t possible for programs that access information using the Internet. Servers can be moved, and IP addresses can be changed or reused for different systems at different times. The getaddrinfo function asks DNS for the address associated with www.google.com on port 80.

Server addresses for specific services usually consist of two parts: the IP address of the computer to connect to and the port of the specific service on that server that you wish to communicate with. The World Wide Web communicates using the HTTP protocol, which is commonly configured to serve data using port 80. You can see in Listing 12-2 that this is the port with which you try to establish a connection on the remote computer.

The getaddrinfo function takes the web address, the port, and two addrinfo structs as parameters. These first of these structs provides the DNS service with some hints as to the type of connection you want to establish with the remote computer. The two most important at this point are the ai_family and ai_socktype fields.

The ai_family field specifies the type of address you would like to retrieve for your program. This allows you to specify whether you want an IPv4, IPv6, NetBIOS, Infrared, or Bluetooth address. The option supplied in Listing 12-2 is unspecified, which allows the getaddrinfofunction to return all the valid IP addresses for the requested web address. These valid IP addresses are represented by the same addrinfo struct and are passed back to the program through the pointer supplied to getaddrinfo’s fourth parameter.

The ai_socktype field lets you specify the type of transmission mechanism to be used with the socket in question. The SOCK_STREAM option in Listing 12-2 creates a socket that uses TCP/IP as the transport mechanism. This type of socket allows you to send packets of information that are guaranteed to arrive in order at the destination. The only other type of transmission mechanism used in this chapter is the SOCK_DGRAM type. This transport mechanism doesn’t guarantee that packets will arrive or that they will arrive in the order expected; however, they don’t have the same overheads that come with the TCP/IP mechanism and therefore can result in packets being sent with much lower latency between computers.

The servinfo returned by the getaddrinfo function can be used to create a socket. A socket file descriptor is obtained from the socket function, which is passed the info from the servinfo structure. The servinfo structure could be a linked list in this instance, because Google supports both the IPv4 and IPv6 address formats. You could write code here that chooses the address to use and acts appropriately. The ai_next field stores a pointer to the next element in the list for as long as the list has more elements. The ai_family, ai_socktype, andai_protocol variables are all passed into the socket function to create a valid socket to use. You can call the connect function once you have a valid socket. The connect function takes the socket ID, the ai_addr field from the servinfo object containing the address, andai_addrlen to determine the length of the address. If the connection wasn’t obtained successfully, you receive a return value of -1 from connect. Listing 12-2 demonstrates this by printing whether the connection was successful.

12-3. Setting Up a Winsock 2 Application in Visual Studio on Windows

Problem

You would like to create a network socket program that can be used on Windows machines.

Solution

Microsoft supplies the Winsock library, which enable socket-based communication between computers.

How It Works

The Windows OS doesn’t come with a native Berkeley Sockets implementation like OS X or Ubuntu. Instead, Microsoft supplies the Winsock library. This library is fortunately very similar to the Berkeley Sockets library, to the extent that most of the code is interchangeable between the three platforms. You can create a new C++ application that uses Winsock by opening Visual Studio and selecting the File image New image Project option. Doing so opens the New Project Wizard shown in Figure 12-6.

9781484201589_Fig12-06.jpg

Figure 12-6. The Visual Studio New Project Wizard

You want to create a Win32 Console Application to run the sample code from this chapter. Select this type of application, enter a name, and choose a folder in which to store the data; then click OK.

You’re taken to the Win32 Application Wizard. Click Next to go to the dialog shown in Figure 12-7.

9781484201589_Fig12-07.jpg

Figure 12-7. The Win32 Application Wizard

Deselect the Precompiled Header and Security Development Lifecycle (SDL) Checks options, and click Finish. When you do, you’re presented with a working project. The project doesn’t support sockets, though, because Windows requires that you link against a library to provide socket support. You can do this by right-clicking the project from the Solution Explorer window and selecting Properties. Specify the libraries to be linked against in the Configuration Properties image Linker image Input section. Figure 12-8 shows this window with specific option selected.

9781484201589_Fig12-08.jpg

Figure 12-8. The Visual Studio linker input options

You want to add a new library to the Additional Dependencies section. Select this option, and click the down arrow to open the dialog shown in Figure 12-9.

9781484201589_Fig12-09.jpg

Figure 12-9. The Additional Dependencies dialog

The Winsock API is provided by the Ws2_32.lib static library. Enter this value in the text box, and click OK. This allows you to use the Winsock 2 API in your program without issue.

Replace the code in the new CPP file with the code from Listing 12-3 to create an application that opens a socket to the Google HTTP web server.

Image Note The code and description that follow are mostly the same as in Recipe 12-1. However, some parts are unique to Windows. If you’ve already read this material, you may wish to cover the Windows-unique aspects and then skip to Recipe 12-4. If you skipped Recipe 12-1 and Recipe 12-2, read on.

Listing 12-3. Opening a Winsock Socket

#include <iostream>
#include <winsock2.h>#include <WS2tcpip.h>

using namespace std;

int main(int argc, char* argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
return 1;
}

addrinfo hints{};
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

// get ready to connect
addrinfo* servinfo{}; // will point to the results
getaddrinfo("www.google.com", "80", &hints, &servinfo);

SOCKET sockfd{ socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol) };
int connectionResult{ connect(sockfd, servinfo->ai_addr, servinfo->ai_addrlen) };
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}

freeaddrinfo(servinfo);

WSACleanup();

return 0;
}

The code sections in bold in Listing 12-3 are unique to the Windows socket library and aren’t transferable to the Unix and OS X implementations of Berkeley Sockets. Windows requires that your program start and shut down the Winsock library. This is achieved using the WSAStartupand WSACleanup functions. Another subtle difference is that the Winsock API specifies the SOCKET type to be an unsigned int. The Berkeley implementations found in OS X and Ubuntu both return a standard int from the socket function. The code in Listing 12-1 and Listing 12-2 uses a type alias to specify the SOCKET type to make the code appear more portable; however, the types still differ between the platforms.

This code requires a short primer on how the Internet works in order for you to fully understand what is happening. Before you can connect to a server, you need to know the address at which it’s located. This is best found using the domain name service (DNS). DNS works by keeping a cache of the server addresses for a given host name. In this example, you’re asking DNS for the address associated with www.google.com. If you’re creating a program to run on your own network, you can specify the IP addresses of the servers manually, but this usually isn’t possible for programs that access information using the Internet. Servers can be moved, and IP addresses can be changed or reused for different systems at different times. The getaddrinfo function asks DNS for the address associated with www.google.com on port 80.

Server addresses for specific services usually consist of two parts: the IP address of the computer to connect to and the port of the specific service on that server that you wish to communicate with. The World Wide Web communicates using the HTTP protocol, which is commonly configured to serve data using port 80. You can see in Listing 12-3 that this is the port with which you try to establish a connection on the remote computer.

The getaddrinfo function takes the web address, the port, and two addrinfo structs as parameters. These first of these structs provides the DNS service with some hints as to the type of connection you want to establish with the remote computer. The two most important at this point are the ai_family and ai_socktype fields.

The ai_family field specifies the type of address you would like to retrieve for your program. This allows you to specify whether you want an IPv4, IPv6, NetBIOS, Infrared, or Bluetooth address. The option supplied in Listing 12-3 is unspecified, which allows the getaddrinfofunction to return all the valid IP addresses for the requested web address. These valid IP addresses are represented by the same addrinfo struct and are passed back to the program through the pointer supplied to getaddrinfo’s fourth parameter.

The ai_socktype field lets you specify the type of transmission mechanism to be used with the socket in question. The SOCK_STREAM option in Listing 12-3 creates a socket that uses TCP/IP as the transport mechanism. This type of socket allows you to send packets of information that are guaranteed to arrive in order at the destination. The only other type of transmission mechanism used in this chapter is the SOCK_DGRAM type. This transport mechanism doesn’t guarantee that packets will arrive or that they will arrive in the order expected; however, they don’t have the same overheads that come with the TCP/IP mechanism and therefore can result in packets being sent with much lower latency between computers.

The servinfo returned by the getaddrinfo function can be used to create a socket. A socket file descriptor is obtained from the socket function, which is passed the info from the servinfo structure. The servinfo structure could be a linked list in this instance, because Google supports both the IPv4 and IPv6 address formats. You could write code here that chooses the address to use and acts appropriately. The ai_next field stores a pointer to the next element in the list for as long as the list has more elements. The ai_family, ai_socktype, andai_protocol variables are all passed into the socket function to create a valid socket to use. You can call the connect function once you have a valid socket. The connect function takes the socket ID, the ai_addr field from the servinfo object containing the address, andai_addrlen to determine the length of the address. If the connection wasn’t obtained successfully, you receive a return value of -1 from connect. Listing 12-3 demonstrates this by printing whether the connection was successful.

12-4. Creating a Socket Connection between Two Programs

Problem

You would like to write a network client program and a server program that can communicate across a network.

Solution

You can use the Berkeley Sockets API to send and receive data across a socket.

How It Works

Berkeley sockets are designed to send and receive information across a network. The API provides send and recv functions to achieve this goal. The difficulty in getting this to work is that you have to ensure that your sockets are configured properly for data transfer. The operations required to receive data are very different than the operations required to send data when setting up your sockets. This recipe also creates code that can run on multiple platforms and compiles using Microsoft Visual Studio, using Xcode, or on a Linux machine using Clang as the compiler.

Image Note The Socket class won’t compile when using GCC because that compiler doesn’t yet support move constructors for the stringstream class. You can alter the sample code to prevent the need to call move with stringstream if you’re using GCC.

The first class to look at starts and stops Winsock when the program is built to run on Windows machines. This class shouldn’t have any effect when you’re building and running on OS X or Linux computers. Listing 12-4 shows how this can be achieved.

Listing 12-4. Wrapping Winsock

#include <iostream>
using namespace std;

#ifdef _MSC_VER

#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <WS2tcpip.h>

#define UsingWinsock 1

using ssize_t = SSIZE_T;

#else

#define UsingWinsock 0

#endif

class WinsockWrapper
{
public:
WinsockWrapper()
{
#if UsingWinsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
exit(1);
}

#ifndef NDEBUG
cout << "Winsock started!" << endl;
#endif
#endif
}

~WinsockWrapper()
{
#if UsingWinsock
WSACleanup();

#ifndef NDEBUG
cout << "Winsock shut down!" << endl;
#endif
#endif
}
};

int main(int argc, char* argv[])
{
WinsockWrapper myWinsockWrapper;

return 0;
}

The code in Listing 12-4 detects the presence of Microsoft Visual Studio using the preprocessor. Visual Studio defines the symbol _MSC_VER when building. You can use this when building a Windows program with Visual Studio to include Windows-specific files as I have done here. The Winsock 2 library is included using a pragma in this program only when Visual Studio is being used to build; the necessary Winsock header files are also included. A define is set up that’s used specifically for this program. When the code is building in Visual Studio, theUsingWinsock macro is defined as 1; when the code isn’t building using Visual Studio, it’s set to 0. Windows builds also require that you create a type alias to map SSIZE_T to ssize_t, because this type uses the lowercase spelling when not building on Windows computers.

The WinsockWrapper class detects the value of UsingWinsock in its constructor and destructor. If this value is 1, then the functions to start and stop the Winsock API are compiled in. This code isn’t compiled in when not building with Visual Studio; therefore it’s safe to include in this manner.

The main function creates a WinsockWrapper object on its first line. This causes the constructor to be called and Winsock to be initialized on Windows machines; it has no effect on non-Windows builds. The Winsock API is also shut down when this object goes out of scope, because the destructor is called. You now have a convenient method for starting and stopping Winsock in a manner that is portable across multiple platforms.

The Socket class is integral to communicating from one program to another. It’s responsible for providing an object-oriented wrapper for the C-based Berkeley Sockets API. The socket itself is represented by a descriptor that is essentially an int. A method creates a class that associates the data needed for creating Berkeley sockets with the code necessary to work with sockets. The entire source for the Socket class is shown in Listing 12-5.

Listing 12-5. Creating an Object-Oriented Socket Class

class Socket
{
private:
#if !UsingWinsock
using SOCKET = int;
#endif

addrinfo* m_ServerInfo{ nullptr };
SOCKET m_Socket{ static_cast<SOCKET>(0xFFFFFFFF) };
sockaddr_storage m_AcceptedSocketStorage{};
socklen_t m_AcceptedSocketSize{ sizeof(m_AcceptedSocketStorage) };

void CreateSocket(string& webAddress, string& port, addrinfo& hints)
{
getaddrinfo(webAddress.c_str(), port.c_str(), &hints, &m_ServerInfo);

m_Socket = socket(
m_ServerInfo->ai_family,
m_ServerInfo->ai_socktype,
m_ServerInfo->ai_protocol);
}

Socket(int newSocket, sockaddr_storage&& socketStorage)
: m_Socket{ newSocket }
, m_AcceptedSocketStorage(move(socketStorage))
{

}

public:
Socket(string& port)
{
#ifndef NDEBUG
stringstream portStream{ port };
int portValue{};
portStream >> portValue;
assert(portValue > 1024); // Ports under 1024 are reserved for certain applications and protocols!
#endif

addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

string address{ "" };
CreateSocket(address, port, hints);
}

Socket(string& webAddress, string& port)
{
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

CreateSocket(webAddress, port, hints);
}

Socket(string& webAddress, string& port, addrinfo& hints)
{
CreateSocket(webAddress, port, hints);
}

~Socket()
{
Close();
}

bool IsValid()
{
return m_Socket != -1;
}

int Connect()
{
int connectionResult{
connect(m_Socket, m_ServerInfo->ai_addr, m_ServerInfo->ai_addrlen)
};

#ifndef NDEBUG
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}
#endif

return connectionResult;
}

int Bind()
{
int bindResult{ ::bind(m_Socket, m_ServerInfo->ai_addr, m_ServerInfo->ai_addrlen) };

#ifndef NDEBUG
if (bindResult == -1)
{
cout << "Bind Failed!" << endl;
}
else
{
cout << "Bind Successful" << endl;
}
#endif

return bindResult;
}

int Listen(int queueSize)
{
int listenResult{ listen(m_Socket, queueSize) };

#ifndef NDEBUG
if (listenResult == -1)
{
cout << "Listen Failed" << endl;
}
else
{
cout << "Listen Succeeded" << endl;
}
#endif

return listenResult;
}

Socket Accept()
{
SOCKET newSocket{
accept(m_Socket,
reinterpret_cast<sockaddr*>(&m_AcceptedSocketStorage),
&m_AcceptedSocketSize)
};

#ifndef NDEBUG
if (newSocket == -1)
{
cout << "Accept Failed" << endl;
}
else
{
cout << "Accept Succeeded" << endl;
}
#endif

m_AcceptedSocketSize = sizeof(m_AcceptedSocketStorage);
return Socket(newSocket, move(m_AcceptedSocketStorage));
}

void Close()
{
#ifdef _MSC_VER
closesocket(m_Socket);
#else
close(m_Socket);
#endif

m_Socket = -1;
freeaddrinfo(m_ServerInfo);
}

ssize_t Send(stringstream data)
{
string packetData{ data.str() };
ssize_t sendResult{ send(m_Socket, packetData.c_str(), packetData.length(), 0) };

#ifndef NDEBUG
if (sendResult == -1)
{
cout << "Send Failed" << endl;
}
else
{
cout << "Send Succeeded" << endl;
}
#endif

return sendResult;
}

stringstream Receive()
{
const int size{ 1024 };
char dataReceived[size];

ssize_t receiveResult{ recv(m_Socket, dataReceived, size, 0) };

#ifndef NDEBUG
if (receiveResult == -1)
{
cout << "Receive Failed" << endl;
}
else if (receiveResult == 0)
{
cout << "Receive Detected Closed Connection!" << endl;
Close();
}
else
{
dataReceived[receiveResult] = '\0';
cout << "Receive Succeeded" << endl;
}
#endif
stringstream data{ dataReceived };
return move(data);
}
};

The Socket class comes with three different constructors, allowing you to create sockets for different purposes. The first public constructor only takes a port as a parameter. This method of construction is suitable for Socket objects used to listen for incoming connections. Thehints addrinfo struct in the constructor sets the ai_flags parameter to the AI_PASSIVE value and passes an empty string for the address. This tells the getaddrinfo function to fill in the local computer’s IP address as the address to use for the socket. Using the local address this way lets you open sockets for listening on a computer—this is an essential task when you wish to receive data in a program from an external source.

The second public constructor takes an address and a port. This lets you create a Socket that automatically uses IPv6 or IPv4 and TCP/IP to create a socket that can be used for sending data. Both the first and second constructors are for convenience—both could be deleted in favor of the third public constructor, which takes an address, a port, and an addrinfo struct and allows the user to configure a Socket as they wish.

The final constructor is a private constructor. This type of constructor is used when an external program connects to a socket listening for connections. You can see how this is used in the Accept method.

The IsValid method determines whether the Socket has been initialized with a proper descriptor. The socket function in CreateSocket returns -1 in the result of a failure; the default value of m_Socket is also -1.

The Connect method is used when you wish to establish a connection to a remote computer and you aren’t interested in receiving connections from other programs. It’s primarily used on the client side of a client-server relationship; however, it’s not inconceivable that you could write peer-to-peer programs that use different sockets for listening and connecting to others. Connect calls the Berkeley connect function but is able to use the m_Socket and m_ServerInfo objects from the object rather than your having to pass them manually from an external location.

The Bind method is used when you wish to receive incoming connections. The Berkeley bind function is responsible for negotiating for access to the port you wish to use with the OS. The OS is responsible for sending and receiving the network traffic, and ports are used for the computer to know which program is waiting for data on which port. The scope operator on the bind function is necessary with this code when the using namespace std; statement is present. This tells the compiler to use the bind method from the global namespace and not from the std namespace. The bind method from the std namespace is used to create functors and has nothing to do with sockets.

The Listen method comes after the call to Bind and tells the socket to begin queuing connections from remote machines. The queueSize parameter specifies the size of the queue; once the queue is full, connections will be dropped from by OS. The number of connections that your OS can support will vary. Desktop OSs generally support many fewer queued connections than server-specific OSs. A number such as 5 is fine for most uses.

The Accept method pulls connections from the queue created when Listen is called. Accept calls the Berkeley accept function, which takes the m_Socket variable as its first parameter. The second and third parameters are the m_AcceptedSocketStorage andm_AcceptedSocketSize variables. The m_AcceptedSocketStorage member variable is of type sockaddr_storage and not the sockaddr type that the accept method expects. The sockaddr_storage type is large enough to handle both IPv4 and IPv6 addresses, but the accept method still expects a pointer to the sockaddr type. This isn’t ideal; however, it can be addressed using a reinterpret_cast, because accept also takes into account the size of the object being passed. The size is altered if the object returned is smaller than the size being passed in; therefore, the size is reset before the function returns. The m_AcceptedSocketStorage object is moved into the new Socket object being returned from the function to ensure that the copy in the initial Socket is invalidated.

The Close method is responsible for shutting down the Socket when it’s no longer needed. The closesocket function is called on Windows, and the close function is used on non-Windows platforms. The freeaddrinfo object is also released in the destructor for the class.

The next method is Send. Unsurprisingly, this method sends data to the machine on the other end of the connection. Send is set up to send a stringstream object for now, because properly serializing binary data is a little outside the scope of this book. You can see that the sendBerkeley function is called with the m_Socket descriptor along with the string data and size pulled from the stringstream object passed in.

The Receive method is responsible for bringing data in from the remote connection. This call blocks until data is ready to be read from the socket connection. The Receive function can return three types of values: -1 when an error has been encountered, 0 when the connection has been closed by the remote computer, or a positive value indicating the number of bytes received. The received data is read into a char array, which is in turn passed into a stringstream object to be returned from the function using a move constructor.

Now that you have a fully functioning Socket class, you can create programs to send and receive data. The code in Listing 12-6 can be used to create a program that waits for a remote connection and a single received message.

Listing 12-6. Creating a Program That Can Receive Data

#include <cassert>
#include <iostream>
#include <type_traits>
#include <vector>

#ifndef NDEBUG
#include <sstream>
#endif

using namespace std;

#ifdef _MSC_VER

#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <WS2tcpip.h>

#define UsingWinsock 1

using ssize_t = SSIZE_T;

#else

#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#define UsingWinsock 0

#endif

class WinsockWrapper
{
public:
WinsockWrapper()
{
#if UsingWinsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
exit(1);
}

cout << "Winsock started!" << endl;
#endif
}

~WinsockWrapper()
{
#if UsingWinsock
WSACleanup();

cout << "Winsock shut down!" << endl;
#endif
}
};

class Socket
{
private:
#if !UsingWinsock
using SOCKET = int;
#endif

addrinfo* m_ServerInfo{ nullptr };
SOCKET m_Socket{ static_cast<SOCKET>(0xFFFFFFFF) };
sockaddr_storage m_AcceptedSocketStorage{};
socklen_t m_AcceptedSocketSize{ sizeof(m_AcceptedSocketStorage) };

void CreateSocket(string& webAddress, string& port, addrinfo& hints)
{
getaddrinfo(webAddress.c_str(), port.c_str(), &hints, &m_ServerInfo);

m_Socket = socket(
m_ServerInfo->ai_family,
m_ServerInfo->ai_socktype,
m_ServerInfo->ai_protocol);
}

Socket(int newSocket, sockaddr_storage&& socketStorage)
: m_Socket{ newSocket }
, m_AcceptedSocketStorage(move(socketStorage))
{

}

public:
Socket(string& port)
{
#ifndef NDEBUG
stringstream portStream{ port };
int portValue{};
portStream >> portValue;
assert(portValue > 1024);
// Ports under 1024 are reserved for certain applications and protocols!
#endif

addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

string address{ "" };
CreateSocket(address, port, hints);
}

Socket(string& webAddress, string& port)
{
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

CreateSocket(webAddress, port, hints);
}

Socket(string& webAddress, string& port, addrinfo& hints)
{
CreateSocket(webAddress, port, hints);
}

~Socket()
{
Close();
}

bool IsValid()
{
return m_Socket != -1;
}

int Connect()
{
int connectionResult{
connect(m_Socket, m_ServerInfo->ai_addr, m_ServerInfo->ai_addrlen)
};

#ifndef NDEBUG
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}
#endif

return connectionResult;
}

int Bind()
{
int bindResult{ ::bind(m_Socket, m_ServerInfo->ai_addr, m_ServerInfo->ai_addrlen) };

#ifndef NDEBUG
if (bindResult == -1)
{
cout << "Bind Failed!" << endl;
}
else
{
cout << "Bind Successful" << endl;
}
#endif

return bindResult;
}

int Listen(int queueSize)
{
int listenResult{ listen(m_Socket, queueSize) };

#ifndef NDEBUG
if (listenResult == -1)
{
cout << "Listen Failed" << endl;
}
else
{
cout << "Listen Succeeded" << endl;
}
#endif

return listenResult;
}

Socket Accept()
{
SOCKET newSocket{
accept(m_Socket,
reinterpret_cast<sockaddr*>(&m_AcceptedSocketStorage),
&m_AcceptedSocketSize)
};

#ifndef NDEBUG
if (newSocket == -1)
{
cout << "Accept Failed" << endl;
}
else
{
cout << "Accept Succeeded" << endl;
}
#endif

m_AcceptedSocketSize = sizeof(m_AcceptedSocketStorage);
return Socket(newSocket, move(m_AcceptedSocketStorage));
}

void Close()
{
#ifdef _MSC_VER
closesocket(m_Socket);
#else
close(m_Socket);
#endif

m_Socket = -1;
freeaddrinfo(m_ServerInfo);
}

ssize_t Send(stringstream data)
{
string packetData{ data.str() };
ssize_t sendResult{ send(m_Socket, packetData.c_str(), packetData.length(), 0) };

#ifndef NDEBUG
if (sendResult == -1)
{
cout << "Send Failed" << endl;
}
else
{
cout << "Send Succeeded" << endl;
}
#endif

return sendResult;
}

stringstream Receive()
{
const int size{ 1024 };
char dataReceived[size];

ssize_t receiveResult{ recv(m_Socket, dataReceived, size, 0) };

#ifndef NDEBUG
if (receiveResult == -1)
{
cout << "Receive Failed" << endl;
}
else if (receiveResult == 0)
{
cout << "Receive Detected Closed Connection!" << endl;
Close();
}
else
{
dataReceived[receiveResult] = '\0';
cout << "Receive Succeeded" << endl;
}
#endif
stringstream data{ dataReceived };
return move(data);
}
};

int main(int argc, char* argv[])
{
WinsockWrapper myWinsockWrapper;

string port{ "3000" };
Socket myBindingSocket(port);
myBindingSocket.Bind();

int listenResult{ myBindingSocket.Listen(5) };
assert(listenResult != -1);

Socket acceptResult{ myBindingSocket.Accept() };
assert(acceptResult.IsValid());

stringstream data{ acceptResult.Receive() };

string message;
getline(data, message, '\0');

cout << "Received Message: " << message << endl;

return 0;
}

The code in Listing 12-6 creates a program that has a socket that waits for a single message to be received from a remote connection. The main function ends up consisting of only a handful of lines of code, thanks to the difficult work wrapped up in the WinsockWrapper and Socketclasses. The main function begins by creating a WinsockWrapper to initialize Winsock if running on a server built by Visual Studio for Windows computers. A Socket is then initialized to port 3000 with an empty address. This port will be used to listen for connections on the local computer. You can see that this is the case, because the main function goes on to call Bind and then Listen with a queue size of 5 before finally calling Accept. The Accept call blocks until a remote connection is present in the queue. Accept returns a separate Socket object that should be used to receive data. The Receive call on that Socket is also a blocking call, and the program waits there until data is available. The program ends by printing out the received message before returning.

Once you have the server program built and running, you need a client program to connect to it and send a message. This is shown in Listing 12-7.

Listing 12-7. The Client Program

#include <cassert>
#include <iostream>
#include <type_traits>

#ifndef NDEBUG
#include <sstream>
#endif

using namespace std;

#ifdef _MSC_VER

#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <WS2tcpip.h>

#define UsingWinsock 1

using ssize_t = SSIZE_T;

#else

#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

#define UsingWinsock 0

#endif

class WinsockWrapper
{
public:
WinsockWrapper()
{
#if UsingWinsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
exit(1);
}

#ifndef NDEBUG
cout << "Winsock started!" << endl;
#endif
#endif
}

~WinsockWrapper()
{
#if UsingWinsock
WSACleanup();

#ifndef NDEBUG
cout << "Winsock shut down!" << endl;
#endif
#endif
}
};

class Socket
{
private:
#if !UsingWinsock
using SOCKET = int;
#endif

addrinfo* m_ServerInfo{ nullptr };
SOCKET m_Socket{ static_cast<SOCKET>(0xFFFFFFFF) };
sockaddr_storage m_AcceptedSocketStorage{};
socklen_t m_AcceptedSocketSize{ sizeof(m_AcceptedSocketStorage) };

void CreateSocket(string& webAddress, string& port, addrinfo& hints)
{
getaddrinfo(webAddress.c_str(), port.c_str(), &hints, &m_ServerInfo);

m_Socket = socket(m_ServerInfo->ai_family,
m_ServerInfo->ai_socktype,
m_ServerInfo->ai_protocol);
}

Socket(int newSocket, sockaddr_storage&& socketStorage)
: m_Socket{ newSocket }
, m_AcceptedSocketStorage(move(socketStorage))
{

}

public:
Socket(string& port)
{
#ifndef NDEBUG
stringstream portStream{ port };
int portValue{};
portStream >> portValue;
assert(portValue > 1024);
// Ports under 1024 are reserved for certain applications and protocols!
#endif

addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

string address{ "" };
CreateSocket(address, port, hints);
}

Socket(string& webAddress, string& port)
{
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

CreateSocket(webAddress, port, hints);
}

Socket(string& webAddress, string& port, addrinfo& hints)
{
CreateSocket(webAddress, port, hints);
}

~Socket()
{
Close();
}

bool IsValid()
{
return m_Socket != -1;
}

int Connect()
{
int connectionResult{ connect(
m_Socket,
m_ServerInfo->ai_addr,
m_ServerInfo->ai_addrlen)
};

#ifndef NDEBUG
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}
#endif

return connectionResult;
}

int Bind()
{
int bindResult{ ::bind(m_Socket, m_ServerInfo->ai_addr, m_ServerInfo->ai_addrlen) };

#ifndef NDEBUG
if (bindResult == -1)
{
cout << "Bind Failed!" << endl;
}
else
{
cout << "Bind Successful" << endl;
}
#endif

return bindResult;
}

int Listen(int queueSize)
{
int listenResult{ listen(m_Socket, queueSize) };

#ifndef NDEBUG
if (listenResult == -1)
{
cout << "Listen Failed" << endl;
}
else
{
cout << "Listen Succeeded" << endl;
}
#endif

return listenResult;
}

Socket Accept()
{
SOCKET newSocket{ accept(m_Socket, reinterpret_cast<sockaddr*>(&m_AcceptedSocketStorage), &m_AcceptedSocketSize) };

#ifndef NDEBUG
if (newSocket == -1)
{
cout << "Accept Failed" << endl;
}
else
{
cout << "Accept Succeeded" << endl;
}
#endif

m_AcceptedSocketSize = sizeof(m_AcceptedSocketStorage);
return Socket(newSocket, move(m_AcceptedSocketStorage));
}

void Close()
{
#ifdef _MSC_VER
closesocket(m_Socket);
#else
close(m_Socket);
#endif

m_Socket = -1;
freeaddrinfo(m_ServerInfo);
}

ssize_t Send(stringstream data)
{
string packetData{ data.str() };
ssize_t sendResult{ send(m_Socket, packetData.c_str(), packetData.length(), 0) };

#ifndef NDEBUG
if (sendResult == -1)
{
cout << "Send Failed" << endl;
}
else
{
cout << "Send Succeeded" << endl;
}
#endif

return sendResult;
}

stringstream Receive()
{
const int size{ 1024 };
char dataReceived[size];

ssize_t receiveResult{ recv(m_Socket, dataReceived, size, 0) };

#ifndef NDEBUG
if (receiveResult == -1)
{
cout << "Receive Failed" << endl;
}
else if (receiveResult == 0)
{
cout << "Receive Detected Closed Connection!" << endl;
Close();
}
else
{
dataReceived[receiveResult] = '\0';
cout << "Receive Succeeded" << endl;
}
#endif
stringstream data{ dataReceived };
return move(data);
}
};

int main(int argc, char* argv[])
{
WinsockWrapper myWinsockWrapper;

string address("192.168.178.44");
string port("3000");
Socket myConnectingSocket(address, port);
myConnectingSocket.Connect();

string message("Sending Data Over a Network!");
stringstream data;
data << message;

myConnectingSocket.Send(move(data));

return 0;
}

Listing 12-7 shows that the same Socket class can be used on the server and the client. The client’s main function also uses the WinsockWrapper object to handle starting and closing the Winsock library. A Socket is then created that connects to IP address 192.168.178.44. (This is the address of the computer I used to host the server program.) The Connect method is called after the Socket is created to establish a connection between the two programs running on different computers. The Send method is the last function call and sends the string “Sending Data Over a Network!” Figure 12-10 shows the output obtained by running the server on a MacBook Pro and the client on a Windows 8.1 desktop PC.

9781484201589_Fig12-10.jpg

Figure 12-10. The output generated by running the server on OS X

12-5. Creating a Networking Protocol between Two Programs

Problem

You would like to create two programs that are able to communicate with each other following standard patterns.

Solution

You can create an agreed-on protocol that both programs can follow so that each knows how to respond to a given request.

How It Works

A socket connection that is established between two programs can be used to send data both ways: from the program that initiated the connection to the receiver, and also back from the receiver to the initiator. This feature allows you to write networked applications that can respond to requests and even build more complicated protocols that require several messages to be sent back and forth in a single application.

The most common example of a protocol in use today that you may be familiar with is HTTP. HTTP is the network protocol that powers the World Wide Web. It’s a request and response protocol that lets a client program request data from a server. Common applications can be seen when a browser requests a web page from a server, but it’s also not uncommon for mobile apps to use HTTP to transfer data between their app and their server back end. Other common protocols are FTP for facilitating file transfers between computers and the POP and SMTP e-mail protocols.

This recipe shows a very simple network protocol that asks a server for a question, has a client respond with an answer, and has the server tell the client whether the answer was correct. This protocol is trivial compared to a complicated example such as HTTP, but it’s an excellent place to start.

The protocol consists of four messages: QUESTION, ANSWER, QUIT, and FINISHED. The QUESTION message is sent from the client to the server when the user should be asked a question. The server responds to this message by sending a question to the client. The client responds to the question by sending ANSWER followed by the user’s answer to the server. The client could instead send QUIT to the server at any time to terminate the socket connection. Once the server has sent all the questions to the server, a subsequent QUESTION request from the client will result inFINISHED being sent to the client; then the connection will be terminated.

The server program in this recipe can handle multiple client connections at a time. It does this by accepting a single connection using the Socket::Accept method and then handing the Socket connected to the client to a thread using the async function. You can see the source for the server program in Listing 12-8.

Listing 12-8. The Protocol Server Program

#include <array>
#include <cassert>
#include <future>
#include <iostream>
#include <thread>
#include <type_traits>
#include <vector>

#ifndef NDEBUG
#include <sstream>
#endif

using namespace std;

#ifdef _MSC_VER

#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <WS2tcpip.h>

#define UsingWinsock 1

using ssize_t = SSIZE_T;

#else

#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#define UsingWinsock 0

#endif

class WinsockWrapper
{
public:
WinsockWrapper()
{
#if UsingWinsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
exit(1);
}

cout << "Winsock started!" << endl;
#endif
}

~WinsockWrapper()
{
#if UsingWinsock
WSACleanup();

cout << "Winsock shut down!" << endl;
#endif
}
};

class Socket
{
private:
#if !UsingWinsock
using SOCKET = int;
#endif

addrinfo* m_ServerInfo{ nullptr };
SOCKET m_Socket{ static_cast<SOCKET>(0xFFFFFFFF) };
sockaddr_storage m_AcceptedSocketStorage{};
socklen_t m_AcceptedSocketSize{ sizeof(m_AcceptedSocketStorage) };

void CreateSocket(string& webAddress, string& port, addrinfo& hints)
{
getaddrinfo(webAddress.c_str(), port.c_str(), &hints, &m_ServerInfo);

m_Socket = socket(m_ServerInfo->ai_family,
m_ServerInfo->ai_socktype,
m_ServerInfo->ai_protocol);
}

Socket(int newSocket, sockaddr_storage&& socketStorage)
: m_Socket{ newSocket }
, m_AcceptedSocketStorage(move(socketStorage))
{

}

public:
Socket(string& port)
{
#ifndef NDEBUG
stringstream portStream{ port };
int portValue{};
portStream >> portValue;
assert(portValue > 1024);
// Ports under 1024 are reserved for certain applications and protocols!
#endif

addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

string address{ "" };
CreateSocket(address, port, hints);
}

Socket(string& webAddress, string& port)
{
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

CreateSocket(webAddress, port, hints);
}

Socket(string& webAddress, string& port, addrinfo& hints)
{
CreateSocket(webAddress, port, hints);
}

~Socket()
{
Close();
}

Socket(const Socket& other) = delete;

Socket(Socket&& other)
: m_ServerInfo( other.m_ServerInfo )
, m_Socket( other.m_Socket )
, m_AcceptedSocketStorage( other.m_AcceptedSocketStorage )
, m_AcceptedSocketSize( other.m_AcceptedSocketSize )
{
if (this != &other)
{
other.m_ServerInfo = nullptr;
other.m_Socket = -1;
other.m_AcceptedSocketStorage = sockaddr_storage{};
other.m_AcceptedSocketSize = sizeof(other.m_AcceptedSocketStorage);
}
}

bool IsValid()
{
return m_Socket != -1;
}

int Connect()
{
int connectionResult{
connect(m_Socket,
m_ServerInfo->ai_addr,
m_ServerInfo->ai_addrlen)
};

#ifndef NDEBUG
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}
#endif

return connectionResult;
}

int Bind()
{
int bindResult{ ::bind(m_Socket, m_ServerInfo->ai_addr, m_ServerInfo->ai_addrlen) };

#ifndef NDEBUG
if (bindResult == -1)
{
cout << "Bind Failed!" << endl;
}
else
{
cout << "Bind Successful" << endl;
}
#endif

return bindResult;
}

int Listen(int queueSize)
{
int listenResult{ listen(m_Socket, queueSize) };

#ifndef NDEBUG
if (listenResult == -1)
{
cout << "Listen Failed" << endl;
}
else
{
cout << "Listen Succeeded" << endl;
}
#endif

return listenResult;
}

Socket Accept()
{
SOCKET newSocket{
accept(m_Socket,
reinterpret_cast<sockaddr*>(&m_AcceptedSocketStorage),
&m_AcceptedSocketSize)
};

#ifndef NDEBUG
if (newSocket == -1)
{
cout << "Accept Failed" << endl;
}
else
{
cout << "Accept Succeeded" << endl;
}
#endif

m_AcceptedSocketSize = sizeof(m_AcceptedSocketStorage);
return Socket(newSocket, move(m_AcceptedSocketStorage));
}

void Close()
{
#ifdef _MSC_VER
closesocket(m_Socket);
#else
close(m_Socket);
#endif

m_Socket = -1;
freeaddrinfo(m_ServerInfo);
}

ssize_t Send(stringstream data)
{
string packetData{ data.str() };
ssize_t sendResult{ send(m_Socket, packetData.c_str(), packetData.length(), 0) };

#ifndef NDEBUG
if (sendResult == -1)
{
cout << "Send Failed" << endl;
}
else
{
cout << "Send Succeeded" << endl;
}
#endif

return sendResult;
}

stringstream Receive()
{
const int size{ 1024 };
char dataReceived[size];

ssize_t receiveResult{ recv(m_Socket, dataReceived, size, 0) };

#ifndef NDEBUG
if (receiveResult == -1)
{
cout << "Receive Failed" << endl;
}
else if (receiveResult == 0)
{
cout << "Receive Detected Closed Connection!" << endl;
Close();
}
else
{
dataReceived[receiveResult] = '\0';
cout << "Receive Succeeded" << endl;
}
#endif
stringstream data{ dataReceived };
return move(data);
}
};

namespace
{
const int NUM_QUESTIONS{ 2 };
const array<string, NUM_QUESTIONS> QUESTIONS
{
"What is the capital of Australia?",
"What is the capital of the USA?"
};
const array<string, NUM_QUESTIONS> ANSWERS{ "Canberra", "Washington DC" };
}

bool ProtocolThread(reference_wrapper<Socket> connectionSocketRef)
{
Socket socket{ move(connectionSocketRef.get()) };

int currentQuestion{ 0 };

string message;
while (message != "QUIT")
{
stringstream sstream{ socket.Receive() };
if (sstream.rdbuf()->in_avail() == 0)
{
break;
}

sstream >> message;

stringstream output;
if (message == "QUESTION")
{
if (currentQuestion >= NUM_QUESTIONS)
{
output << "FINISHED";
socket.Send(move(output));

cout << "Quiz Complete!" << endl;
break;
}

output << QUESTIONS[currentQuestion];
}
else if (message == "ANSWER")
{
string answer;
sstream >> answer;

if (answer == ANSWERS[currentQuestion])
{
output << "You are correct!";
}
else
{
output << "Sorry the correct answer is " << ANSWERS[currentQuestion];
}
++currentQuestion;
}
socket.Send(move(output));
}

return true;
}

int main(int argc, char* argv[])
{
WinsockWrapper myWinsockWrapper;

string port("3000");
Socket myListeningSocket(port);

int bindResult{ myListeningSocket.Bind() };
assert(bindResult != -1);
if (bindResult != -1)
{
int listenResult{ myListeningSocket.Listen(5) };
assert(listenResult != -1);
if (listenResult != -1)
{
while (true)
{
Socket acceptedSocket{ myListeningSocket.Accept() };
async(launch::async, ProtocolThread, ref(acceptedSocket));
}
}
}

return 0;
}

The server program in Listing 12-8 uses the same Socket class that is described in detail in Recipe 12-4. The main function is responsible for handling multiple clients simultaneously. It does this by creating a Socket and binding it to port 3000. The bound Socket is then asked to listen for incoming connections; it does so with a queue length of 5. The final part of main uses a while loop to accept any incoming connections and hands them off to the async function. The async function creates a thread to handle each Socket retrieved fromSocket::Accept; the first parameter is launch::async.

The ProtocolThread function responds to the requests of the connected client and upholds the server side of the simple quiz network protocol. Data is transferred between the client and the server by packing a string into each packet. The message variable holds individual messages from a stringstream. This protocol can be handled with a basic if...else if block. When the QUESTION message is received, the server packs the current question into the output stringstream. If the message is ANSWER, then the server checks whether the user is correct and packs the appropriate response into output. The output stringstream is sent to the client using the same Socket that received the data initially, showing that a Socket connection need not necessarily be a one-way communication channel. If the QUESTION message is received and all the questions that the server has available have been sent, then the server sends the client the FINISHED message and breaks out of the loop; this causes the Socket to fall out of scope and in turn closes the connection.

All of this activity requires a client to be connected to communicate with the server program. You can see a basic client implementation in Listing 12-9.

Listing 12-9. A Simple Quiz Protocol Client

#include <cassert>
#include <iostream>
#include <type_traits>

#ifndef NDEBUG
#include <sstream>
#endif

using namespace std;

#ifdef _MSC_VER

#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <WS2tcpip.h>

#define UsingWinsock 1

using ssize_t = SSIZE_T;

#else

#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#define UsingWinsock 0

#endif

class WinsockWrapper
{
public:
WinsockWrapper()
{
#if UsingWinsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
exit(1);
}

cout << "Winsock started!" << endl;
#endif
}

~WinsockWrapper()
{
#if UsingWinsock
WSACleanup();

cout << "Winsock shut down!" << endl;
#endif
}
};

class Socket
{
private:
#if !UsingWinsock
using SOCKET = int;
#endif

addrinfo* m_ServerInfo{ nullptr };
SOCKET m_Socket{ static_cast<SOCKET>(0xFFFFFFFF) };
sockaddr_storage m_AcceptedSocketStorage{};
socklen_t m_AcceptedSocketSize{ sizeof(m_AcceptedSocketStorage) };

void CreateSocket(string& webAddress, string& port, addrinfo& hints)
{
getaddrinfo(webAddress.c_str(), port.c_str(), &hints, &m_ServerInfo);

m_Socket = socket(
m_ServerInfo->ai_family,
m_ServerInfo->ai_socktype,
m_ServerInfo->ai_protocol);
}

Socket(int newSocket, sockaddr_storage&& socketStorage)
: m_Socket{ newSocket }
, m_AcceptedSocketStorage(move(socketStorage))
{

}

public:
Socket(string& port)
{
#ifndef NDEBUG
stringstream portStream{ port };
int portValue{};
portStream >> portValue;
assert(portValue > 1024);
// Ports under 1024 are reserved for certain applications and protocols!
#endif

addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

string address{ "" };
CreateSocket(address, port, hints);
}

Socket(string& webAddress, string& port)
{
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

CreateSocket(webAddress, port, hints);
}

Socket(string& webAddress, string& port, addrinfo& hints)
{
CreateSocket(webAddress, port, hints);
}

~Socket()
{
Close();
}

Socket(const Socket& other) = delete;

Socket(Socket&& other)
: m_ServerInfo(other.m_ServerInfo)
, m_Socket(other.m_Socket)
, m_AcceptedSocketStorage(other.m_AcceptedSocketStorage)
, m_AcceptedSocketSize(other.m_AcceptedSocketSize)
{
if (this != &other)
{
other.m_ServerInfo = nullptr;
other.m_Socket = -1;
other.m_AcceptedSocketStorage = sockaddr_storage{};
other.m_AcceptedSocketSize = sizeof(other.m_AcceptedSocketStorage);
}
}

bool IsValid()
{
return m_Socket != -1;
}

int Connect()
{
int connectionResult{ connect(
m_Socket,
m_ServerInfo->ai_addr,
m_ServerInfo->ai_addrlen)
};

#ifndef NDEBUG
if (connectionResult == -1)
{
cout << "Connection failed!" << endl;
}
else
{
cout << "Connection successful!" << endl;
}
#endif

return connectionResult;
}

int Bind()
{
int bindResult{ ::bind(m_Socket, m_ServerInfo->ai_addr, m_ServerInfo->ai_addrlen) };

#ifndef NDEBUG
if (bindResult == -1)
{
cout << "Bind Failed!" << endl;
}
else
{
cout << "Bind Successful" << endl;
}
#endif

return bindResult;
}

int Listen(int queueSize)
{
int listenResult{ listen(m_Socket, queueSize) };

#ifndef NDEBUG
if (listenResult == -1)
{
cout << "Listen Failed" << endl;
}
else
{
cout << "Listen Succeeded" << endl;
}
#endif

return listenResult;
}

Socket Accept()
{
SOCKET newSocket{ accept(
m_Socket,
reinterpret_cast<sockaddr*>(&m_AcceptedSocketStorage),
&m_AcceptedSocketSize)
};

#ifndef NDEBUG
if (newSocket == -1)
{
cout << "Accept Failed" << endl;
}
else
{
cout << "Accept Succeeded" << endl;
}
#endif

m_AcceptedSocketSize = sizeof(m_AcceptedSocketStorage);
return Socket(newSocket, move(m_AcceptedSocketStorage));
}

void Close()
{
#ifdef _MSC_VER
closesocket(m_Socket);
#else
close(m_Socket);
#endif

m_Socket = -1;
freeaddrinfo(m_ServerInfo);
}

ssize_t Send(stringstream data)
{
string packetData{ data.str() };
ssize_t sendResult{ send(m_Socket, packetData.c_str(), packetData.length(), 0) };

#ifndef NDEBUG
if (sendResult == -1)
{
cout << "Send Failed" << endl;
}
else
{
cout << "Send Succeeded" << endl;
}
#endif

return sendResult;
}

stringstream Receive()
{
const int size{ 1024 };
char dataReceived[size];

ssize_t receiveResult{ recv(m_Socket, dataReceived, size, 0) };

#ifndef NDEBUG
if (receiveResult == -1)
{
cout << "Receive Failed" << endl;
}
else if (receiveResult == 0)
{
cout << "Receive Detected Closed Connection!" << endl;
Close();
}
else
{
dataReceived[receiveResult] = '\0';
cout << "Receive Succeeded" << endl;
}
#endif
stringstream data{ dataReceived };
return move(data);
}
};

int main(int argc, char* argv[])
{
WinsockWrapper myWinsockWrapper;

string address("192.168.178.44");
string port("3000");
Socket mySocket(address, port);
int connectionResult{ mySocket.Connect() };
if (connectionResult != -1)
{
stringstream output{ "QUESTION" };
mySocket.Send(move(output));

stringstream input{ mySocket.Receive() };
if (input.rdbuf()->in_avail() > 0)
{
string question;
getline(input, question, '\0');
input.clear();

while (question != "FINISHED")
{
cout << question << endl;

string answer;
cin >> answer;

output << "ANSWER ";
output << answer;
mySocket.Send(move(output));

input = mySocket.Receive();
if (input.rdbuf()->in_avail() == 0)
{
break;
}

string result;
getline(input, result, '\0');
cout << result << endl;

output << "QUESTION";
mySocket.Send(move(output));

input = mySocket.Receive();
getline(input, question, '\0');
input.clear();
}
}
}

return 0;
}

The client program in Listing 12-9 can connect to the server in Listing 12-8 and present the server quiz to the player. The client code is simpler than the server because it only has to consider a single connection and therefore has no need for threads or handling multiple sockets. The client does need to know the address of the server to connect to; the IP address is the IP for the MacBook Pro I have running the server on my home network. The client sends QUESTION to the server and then waits for a response in the Receive call. Receive is a blocking call; therefore, the client sits and waits until the data is available. It then gets input from the player to send back to the server and waits for the response regarding whether the user is correct. This process is repeated in a loop until the server notifies the client that the quiz has ended.

The beauty of network protocols implemented in this manner is that they can be reused in different programs. If you wanted to extend this example, you could easily create a GUI version using a framework such as Qt, make all the calls to Receive occur in a thread, and have the UI animate a spinning logo to indicate to the user that the program is waiting for data to come across a remote connection. You could also extend the server application to store results and add to the protocol to let users restart quizzes that were in progress. In the end, the protocol simply specifies how two programs should communicate with each other to facilitate providing a service from one computer to another.