A guide to getting started with boost::asio
7. Networking basics: connectors and acceptors (TCP)
The first concepts of the boost::asio networking system we will cover will be TCP programming. The nice thing about the boost::asio library is consistency. Having covered the other aspects of the boost::asio library, we already know and understand the framework the network system uses. We just have to learn the specific networking API functions!
To get started, we will see how to connect synchronously to a host. Since our program will be acting as a client, we will be using the tcp::socket object now. There are different socket types for the different protocols available. As a result, we must make sure we use the correct objects and functions from the correct namespace. Before we can connect to a remote host, we must be able to get the address of the remote host. To do this, we will make use of tcp::resolver.
This example will simply open a connection to Google. The program will tell us the actual IP and port it is attempting to connect to. If we open a command prompt and run the command "netstat -n", we should see a TCP connection of the program. In this example, we use the format for the query object to have reusable code. It might be common for the port to be an integer rather than a string, so we make use of lexical_cast to convert it to a string. While there are other methods to do this, boost makes it quick and easy. For another example, make sure to take a look at the Daytime.1 - A synchronous TCP daytime client tutorial on the boost site as well.
Sometimes we might not want to connect synchronously to a remote host. Imagine a GUI application where we want to start a connection via a button, but we do not want the GUI to freeze while the operation completes. Boost::asio provides a way to connect asynchronously through the socket as well. Using the techniques we have already learned, such as using boost::bind and boost::shared_ptr, we can setup our own connect handler that will be invoked.
Just to make sure we are still on the same page, figuratively speaking, we have to use a boost::shared_ptr with most boost::asio objects if we wish to pass them around. This is because the objects themselves are non-copyable and we have to ensure the object remains valid while the handler is waiting to be called. We use boost::bind to setup our own custom handler as well. This handler can have any number of parameters in addition to the default number it has to have. In this case, the async_connect handler is a ConnectHandler, which is simply a template parameter, but referring to the documentation we will see the signature it must have at minimal.
* @param handler The handler to be called when the connection operation
* completes. Copies will be made of the handler as required. The function
* signature of the handler must be:
* @code void handler(
* const boost::system::error_code& error // Result of operation
* ); @endcode
* Regardless of whether the asynchronous operation completes immediately or
* not, the handler will not be invoked from within this function. Invocation
* of the handler will be performed in a manner equivalent to using
One cool thing to note is that with boost::bind, we can rearrange the order of parameters as we desire! All that matters is that the parameter is physically there in the end. Hopefully now, the example makes sense as to why we do certain things. In this example as the last, we still resolve the remote address synchronously. We can change this to an asynchronous method by referring to the documentation if we wish. I prefer to keep it simple and just do the look-ups synchronously.
Now we have two different methods we can use to connect to remote hosts. What about letting remote hosts connect to us? To setup such a "server", we will make use of the tcp::acceptor object. While we covered both synchronous and asynchronous methods for connecting, we will just briefly cover the asynchronous method for the server. The reason is, the main goal to using the boost::asio library is asynchronicity, even though it does provide synchronous methods. We will see examples of more variations later but for now we will cover what will be used the most. Let us take a look at the start of the server.
This example looks pretty close to the previous. In fact, very little has changed! As mentioned before, this is one of the nice things about the boost::asio library. As we take the time to learn the different components, we began to be able to easily understand the others because of the dependency of the components on each other. Once we run the program, we have a server running on port 7777. We can run the command "telnet localhost 7777" to start a connection to the server and trigger the OnAccept function.
However, the server will not accept any more connections. This is because we only call async_accept once and only have one socket object. We will address the design strategies for servers later, as we are only getting started with the core API needed right now. At this time, we can also briefly mention the close and shutdown functions we have seen. In our examples, we have chosen to use the error_code variable version to ensure no exceptions are thrown. This is because sometimes shutdown might not be valid on a socket (if it was not connected or accepted) while close is. If we use exception handling, one of the functions might not get called as a result. So we will simply try to call both functions for sockets and ignore the errors.
The basics of connecting and accepting have been covered now. We now know how to connect to a remote host as well as accept an incoming connection. However, we have not yet covered reading and writing to sockets yet. That will be the next topic of our focus.