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

Accepting connections


When the client application wants to communicate to the server application over a TCP protocol, it first needs to establish a logical connection with that server. In order to do that, the client allocates an active socket and issues a connect command on it (for example by calling the connect() method on the socket object), which leads to a connection establishment request message being sent to the server.

On the server side, some arrangements must be performed before the server application can accept and handle the connection requests arriving from the clients. Before that, all connection requests targeted at this server application are rejected by the operating system.

First, the server application creates and opens an acceptor socket and binds it to the particular endpoint. At this point, the client's connection requests arriving at the acceptor socket's endpoint are still rejected by the operating system. For the operating system to start accepting connection requests targeted at particular endpoint associated with particular acceptor socket, that acceptor socket must be switched into listening mode. After that, the operating system allocates a queue for pending connection requests associated with this acceptor socket and starts accepting connection request addressed to it.

When a new connection request arrives, it is initially received by the operating system, which puts it to the pending connection requests queue associated with an acceptor socket being the connection request's target. When in the queue, the connection request is available to the server application for processing. The server application, when ready to process the next connection request, de-queues one and processes it.

Note that the acceptor socket is only used to establish connections with client applications and is not used in the further communication process. When processing a pending connection request, the acceptor socket allocates a new active socket, binds it to an endpoint chosen by the operating system, and connects it to the corresponding client application that has issued that connection request. Then, this new active socket is ready to be used for communication with the client. The acceptor socket becomes available to process the next pending connection request.

This recipe describes how to switch an acceptor socket into listening mode and accept incoming connection requests in a TCP server application using Boost.Asio.

How to do it…

The following algorithm describes how to set up an acceptor socket so that it starts listening for incoming connections and then how to use it to synchronously process the pending connection request. The algorithm assumes that only one incoming connection will be processed in synchronous mode:

  1. Obtain the port number on which the server will receive incoming connection requests.

  2. Create a server endpoint.

  3. Instantiate and open an acceptor socket.

  4. Bind the acceptor socket to the server endpoint created in step 2.

  5. Call the acceptor socket's listen() method to make it start listening for incoming connection requests on the endpoint.

  6. Instantiate an active socket object.

  7. When ready to process a connection request, call the acceptor socket's accept() method passing an active socket object created in step 6 as an argument.

  8. If the call succeeds, the active socket is connected to the client application and is ready to be used for communication with it.

The following code sample demonstrates possible implementation of the server application that follows the algorithm. Here, we assume that the server is intended to communicate over the TCP protocol with IPv4 as the underlying protocol:

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

using namespace boost;

int main()
{
  // The size of the queue containing the pending connection
  // requests.
  const int BACKLOG_SIZE = 30;

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

  // Step 2. Creating a server endpoint.
  asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(),
    port_num);

  asio::io_service ios;

  try {
    // Step 3. Instantiating and opening an acceptor socket.
    asio::ip::tcp::acceptor acceptor(ios, ep.protocol());

    // Step 4. Binding the acceptor socket to the 
    // server endpint.
    acceptor.bind(ep);

    // Step 5. Starting to listen for incoming connection
    // requests.
    acceptor.listen(BACKLOG_SIZE);

    // Step 6. Creating an active socket.
    asio::ip::tcp::socket sock(ios);

    // Step 7. Processing the next connection request and 
    // connecting the active socket to the client.
    acceptor.accept(sock);

    // At this point 'sock' socket is connected to 
    //the client application and can be used to send data to
    // or receive data from it.
  }
  catch (system::system_error &e) {
    std::cout << "Error occured! Error code = " << e.code()
      << ". Message: " << e.what();

    return e.code().value();
  }

  return 0;
}

How it works…

In step 1, we obtain the protocol port number to which the server application binds its acceptor socket. Here, we assume that the port number has already been obtained and is available at the beginning of the sample.

In step 2, we create a server endpoint that designates all IP addresses available on the host running the server application and a specific protocol port number.

Then in step 3, we instantiate and open an acceptor socket and bind it to the server endpoint in step 4.

In step 5, we call the acceptor's listen() method passing the BACKLOG_SIZE constant value as an argument. This call switches the acceptor socket into the state in which it listens for incoming connection requests. Unless we call the listen() method on the acceptor object, all connection requests arriving at corresponding endpoint will be rejected by the operating system network software. The application must explicitly notify the operating system that it wants to start listening for incoming connection requests on specific endpoint by this call.

The argument that the listen() method accepts as an argument specifies the size of the queue maintained by the operating system to which it puts connection requests arriving from the clients. The requests stay in the queue and are waiting for the server application to de-queue and process them. When the queue becomes full, the new connection requests are rejected by the operating system.

In step 6, we create an active socket object without opening it. We'll need it in step 7.

In step 7, we call the acceptor socket's accept() method. This method accepts an active socket as an argument and performs several operations. First, it checks the queue associated with the acceptor socket containing pending connection requests. If the queue is empty, the method blocks execution until a new connection request arrives to an endpoint to which the acceptor socket is bound and the operating system puts it in the queue.

If at least one connection request is available in the queue, the one on the top of the queue is extracted from it and processed. The active socket that was passed to the accept() method as an argument is connected to the corresponding client application which issued the connection request.

If the connection establishment process succeeds, the accept() method returns and the active socket is opened and connected to the client application and can be used to send data to and receive data from it.

Note

Remember that the acceptor socket doesn't connect itself to the client application while processing a connection request. Instead, it opens and connects another active socket, which is then used for communication with the client application. The acceptor socket only listens for and processes (accepts) incoming connection requests.

Note that UDP servers don't use acceptor sockets because the UDP protocol doesn't imply connection establishment. Instead, an active socket is used that is bound to an endpoint and listens for incoming I/O messages, and this same active socket is used for communication.

See also

  • The Creating a passive socket recipe provides information about passive sockets and demonstrates how to create and open them

  • The Creating an endpoint recipe provides more information on endpoints

  • The Creating an active socket recipe explains how to create and open a socket and provides more details about the asio::io_service class

  • The Binding a socket recipe provides more information about socket binding