Book Image

Boost.Asio C++ Network Programming Cookbook

By : Dmytro Radchuk
Book Image

Boost.Asio C++ Network Programming Cookbook

By: Dmytro Radchuk

Overview of this book

Starting with recipes demonstrating the execution of basic Boost.Asio operations, the book goes on to provide ready-to-use implementations of client and server applications from simple synchronous ones to powerful multithreaded scalable solutions. Finally, you are presented with advanced topics such as implementing a chat application, implementing an HTTP client, and adding SSL support. All the samples presented in the book are ready to be used in real projects just out of the box. As well as excellent practical examples, the book also includes extended supportive theoretical material on distributed application design and construction.
Table of Contents (13 chapters)
Boost.Asio C++ Network Programming Cookbook
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Creating an endpoint


A typical client application, before it can communicate with a server application to consume its services, must obtain the IP address of the host on which the server application is running and the protocol port number associated with it. A pair of values consisting of an IP address and a protocol port number that uniquely identifies a particular application running on a particular host in a computer network is called an endpoint.

The client application will usually obtain the IP address and the port number identifying the server application either from the user directly through the application UI or as command-line arguments or will read it from the application's configuration file.

The IP address can be represented as a string containing an address in dot-decimal notation if it is an IPv4 address (for example, 192.168.10.112) or in hexadecimal notation if it is an IPv6 address (for example, FE36::0404:C3FA:EF1E:3829). Besides, the server IP address can be provided to the client application in an indirect form, as a string containing a DNS name (for example, localhost or www.google.com). Another way to represent an IP address is an integer value. The IPv4 address is represented as a 32-bit integer and IPv6 as a 64-bit integer. However, due to poor readability and memorability this representation is used extremely rarely.

If the client application is provided with a DNS name before it can communicate with the server application, it must resolve the DNS name to obtain the actual IP address of the host running the server application. Sometimes, the DNS name may map to multiple IP addresses, in which case the client may want to try addresses one by one until it finds the one that works. We'll consider a recipe describing how to resolve DNS names with Boost.Asio later in this chapter.

The server application needs to deal with endpoints too. It uses the endpoint to specify to the operating system on which the IP address and protocol port it wants to listen for incoming messages from the clients. If the host running the server application has only one network interface and a single IP address assigned to it, the server application has only one option as to on which address to listen. However, sometimes the host might have more than one network interface and correspondingly more than one IP address. In this situation, the server application encounters a difficult problem of selecting an appropriate IP address on which to listen for incoming messages. The problem is that the application knows nothing about details such as underlying IP protocol settings, packet routing rules, DNS names which are mapped to the corresponding IP addresses, and so on. Therefore, it is quite a complex task (and sometimes even not solvable) for the server application to foresee through which IP address the messages sent by clients will be delivered to the host.

If the server application chooses only one IP address to listen for incoming messages, it may miss messages routed to other IP addresses of the host. Therefore, the server application usually wants to listen on all IP addresses available on the host. This guarantees that the server application will receive all messages arriving at any IP address and the particular protocol port.

To sum up, the endpoints serve two goals:

  • The client application uses an endpoint to designate a particular server application it wants to communicate with.

  • The server application uses an endpoint to specify a local IP address and a port number on which it wants to receive incoming messages from clients. If there is more than one IP address on the host, the server application will want to create a special endpoint representing all IP addresses at once.

This recipe explains how to create endpoints in Boost.Asio both in client and server applications.

Getting ready

Before creating the endpoint, the client application must obtain the raw IP address and the protocol port number designating the server it will communicate with. The server application on the other hand, as it usually listens for incoming messages on all IP addresses, only needs to obtain a port number on which to listen.

Here, we don't consider how the application obtains a raw IP address or a port number. In the following recipes, we assume that the IP address and the port number have already been obtained by the application and are available at the beginning of the corresponding algorithm.

How to do it…

The following algorithms and corresponding code samples demonstrate two common scenarios of creating an endpoint. The first one demonstrates how the client application can create an endpoint to specify the server it wants to communicate with. The second one demonstrates how the server application creates an endpoint to specify on which IP addresses and port it wants to listen for incoming messages from clients.

Creating an endpoint in the client to designate the server

The following algorithm describes steps required to perform in the client application to create an endpoint designating a server application the client wants to communicate with. Initially, the IP address is represented as a string in the dot-decimal notation if this is an IPv4 address or in hexadecimal notation if this is an IPv6 address:

  1. Obtain the server application's IP address and port number. The IP address should be specified as a string in the dot-decimal (IPv4) or hexadecimal (IPv6) notation.

  2. Represent the raw IP address as an object of the asio::ip::address class.

  3. Instantiate the object of the asio::ip::tcp::endpoint class from the address object created in step 2 and a port number.

  4. The endpoint is ready to be used to designate the server application in Boost.Asio communication related methods.

The following code sample demonstrates possible implementation of the algorithm:

#include <boost/asio.hpp>
#include <iostream>

using namespace boost;

int main()
{
  // Step 1. Assume that the client application has already 
  // obtained the IP-address and the protocol port number.
  std::string raw_ip_address = "127.0.0.1";
  unsigned short port_num = 3333;

  // Used to store information about error that happens
  // while parsing the raw IP-address.
  boost::system::error_code ec;
  // Step 2. Using IP protocol version independent address
  // representation.
  asio::ip::address ip_address =
    asio::ip::address::from_string(raw_ip_address, ec);

  if (ec.value() != 0) {
    // Provided IP address is invalid. Breaking execution.
    std::cout 
      << "Failed to parse the IP address. Error code = "
      << ec.value() << ". Message: " << ec.message();
      return ec.value();
  }

  // Step 3.
  asio::ip::tcp::endpoint ep(ip_address, port_num);

  // Step 4. The endpoint is ready and can be used to specify a 
  // particular server in the network the client wants to 
  // communicate with.
  
  return 0;
}

Creating the server endpoint

The following algorithm describes steps required to perform in a server application to create an endpoint specifying all IP addresses available on the host and a port number on which the server application wants to listen for incoming messages from the clients:

  1. Obtain the protocol port number on which the server will listen for incoming requests.

  2. Create a special instance of the asio::ip::address object representing all IP addresses available on the host running the server.

  3. Instantiate an object of the asio::ip::tcp::endpoint class from the address object created in step 2 and a port number.

  4. The endpoint is ready to be used to specify to the operating system that the server wants to listen for incoming messages on all IP addresses and a particular protocol port number.

The following code sample demonstrates possible implementation of the algorithm. Note that it is assumed that the server application is going to communicate over the IPv6 protocol:

#include <boost/asio.hpp>
#include <iostream>

using namespace boost;

int main()
{
  // Step 1. Here we assume that the server application has
  //already obtained the protocol port number.
  unsigned short port_num = 3333;

  // Step 2. Create special object of asio::ip::address class
  // that specifies all IP-addresses available on the host. Note
  // that here we assume that server works over IPv6 protocol.
  asio::ip::address ip_address = asio::ip::address_v6::any();

  // Step 3.
  asio::ip::tcp::endpoint ep(ip_address, port_num);

  // Step 4. The endpoint is created and can be used to 
  // specify the IP addresses and a port number on which 
  // the server application wants to listen for incoming 
  // connections.

  return 0;
}

How it works…

Let's consider the first code sample. The algorithm it implements is applicable in an application playing a role of a client that is an application that actively initiates the communication session with a server. The client application needs to be provided an IP address and a protocol port number of the server. Here we assume that those values have already been obtained and are available at the beginning of the algorithm, which makes step 1 details a given.

Having obtained the raw IP address, the client application must represent it in terms of the Boost.Asio type system. Boost.Asio provides three classes used to represent an IP address:

  • asio::ip::address_v4: This represents an IPv4 address

  • asio::ip::address_v6: This represents an IPv6 address

  • asio::ip::address: This IP-protocol-version-agnostic class can represent both IPv4 and IPv6 addresses

In our sample, we use the asio::ip::address class, which makes the client application IP-protocol-version-agnostic. This means that it can transparently work with both IPv4 and IPv6 servers.

In step 2, we use the asio::ip::address class's static method, from_string(). This method accepts a raw IP address represented as a string, parses and validates the string, instantiates an object of the asio::ip::address class, and returns it to the caller. This method has four overloads. In our sample we use this one:

static asio::ip::address from_string(
    const std::string & str,
    boost::system::error_code & ec);

This method is very useful as it checks whether the string passed to it as an argument contains a valid IPv4 or IPv6 address and if it does, instantiates a corresponding object. If the address is invalid, the method will designate an error through the second argument. It means that this function can be used to validate the raw user input.

In step 3, we instantiate an object of the boost::asio::ip::tcp::endpoint class, passing the IP address and a protocol port number to its constructor. Now, the ep object can be used to designate a server application in the Boost.Asio communication related functions.

The second sample has a similar idea, although it somewhat differs from the first one. The server application is usually provided only with the protocol port number on which it should listen for incoming messages. The IP address is not provided because the server application usually wants to listen for the incoming messages on all IP addresses available on the host, not only on a specific one.

To represent the concept of all IP addresses available on the host, the classes asio::ip::address_v4 and asio::ip::address_v6 provide a static method any(), which instantiates a special object of corresponding class representing the concept. In step 2, we use the any() method of the asio::ip::address_v6 class to instantiate such a special object.

Note that the IP-protocol-version-agnostic class asio::ip::address does not provide the any() method. The server application must explicitly specify whether it wants to receive requests either on IPv4 or on IPv6 addresses by using the object returned by the any() method of either the asio::ip::address_v4 or asio::ip::address_v6 class correspondingly. In step 2 of our second sample, we assume that our server communicates over IPv6 protocol and therefore called the any() method of the asio::ip::address_v6 class.

In step 3, we create an endpoint object which represents all IP addresses available on the host and a particular protocol port number.

There's more...

In both our previous samples we used the endpoint class declared in the scope of the asio::ip::tcp class. If we look at the declaration of the asio::ip::tcp class, we'll see something like this:

class tcp
{
public:
  /// The type of a TCP endpoint.
  typedef basic_endpoint<tcp> endpoint;

  //...
}

It means that this endpoint class is a specialization of the basic_endpoint<> template class that is intended for use in clients and servers communicating over the TCP protocol.

However, creating endpoints that can be used in clients and servers that communicate over the UDP protocol is just as easy. To represent such an endpoint, we need to use the endpoint class declared in the scope of the asio::ip::udp class. The following code snippet demonstrates how this endpoint class is declared:

class udp
{
public:
  /// The type of a UDP endpoint.
  typedef basic_endpoint<udp> endpoint;

  //...
}

For example, if we want to create an endpoint in our client application to designate a server with which we want to communicate over the UDP protocol, we would only slightly change the implementation of step 3 in our sample. This is how that step would look like with changes highlighted:

// Step 3.
asio::ip::udp::endpoint ep(ip_address, port_num);

All other code would not need to be changed as it is transport protocol independent.

The same trivial change in the implementation of step 3 in our second sample is required to switch from a server communicating over TCP to one communicating over UDP.

See also

  • The Binding a socket to an endpoint recipe explains how the endpoint object is used in a server application

  • The Connecting a socket recipe explains how the endpoint object is used in a client application