In this tutorial we’ll learn how to write a client for a feature limited chat server in C++ using the Boost libraries. The codebase for this tutorial is a bit dated as many of the features included by Boost are now apart of the C++ standard library but knowledege of Boost is still worthy of merit in case you run into the many C++ projects which make use of it.
We’ll be making use of the Boost C++ libraries for this tutorial so before we begin make sure you have read through the installation documentation on the Boost website on. Below I have included some links for each operating system on how to install a recent version of Boost; its not extremely difficult but sometimes installing Boost can be tricky for those of us not familiar with compiling C++ projects from source code or the difference between the header-file only libraries and the rest of Boost.
- Windows users Boost Installation Guide
- Ubuntu/Debian Linux can use the aptitude Boost
- Ubuntu/Debian Linux can also use the Upstream Boost which is recommended
- Mac OS X should use Homebrew
If you need extra help installing the Boost C++ libraries or making sure that you can compile a project using Boost and properly link the libraries Google and or Stackoverflow are your best bets. Once you’re comfortable building projects and or compiling single files with Boost we’re ready to begin.
Our client code for the Chat application will rely upon the use of threads to divide our code into three distinct sub-routines.
- A thread for displaying chat messages
- Another thread for sending messages to the Chat server
- And a third thread for receiving messages routed by the server from other connected clients
There are many approaches to designing multi-threaded applications and its a big topic that often rears its head during interviews with top tech companies but for the sake of this tutorial we’ll only need to concern ourselves with just a small subset of concurrent programming. For the purposes of the application we’re going to build in this tutorial series we’re going to be using what is known as the “Producer-Consumer” pattern for organizing our code. As a side note for the pedantics reading this article I’m not claiming that this code will follow Producer-Consumer to the exact sepcification but for the most part it will resemble a typical Producer-Consumer setup.
Quoting from the Wikipedia page on the pattern…
In computing, the producer–consumer’s problem (also known as the bounded-buffer problem) is a classic example of a multi-process synchronization problem. The problem describes two processes, the producer and the consumer, who share a common, fixed-size buffer used as a queue.
Thus the following is a list of C++ objects used by our Client to implement the Producer-Consumer pattern.
boost::thread_group to address multi-processing
std::queue< std::string > to represent the bounded-buffer of messages
boost::thread objects for adding and removing messages from the queue
Lets start off, create a new working directory called chat_app wherever you want on your system.
Next change into the chat_app directory and create a new file, name it boostChatClient.cpp.
First we’ll need to include a few libraries; some from the C++ standard template library and others from Boost.
The above library includes are fairly basic for a C++ console application but unfamiliar to most are probably the Boost includes.
boost/thread Multithreading support
boost/bind A library for functional programming but used here to create sub-rountines for threads
boost/asio System socket and network programming library
boost/algorithm/string Pretty self-explainatory; gives use some new string methods
I will be using namespace aliasing in this application, it can be a pain sometimes to read code without namespace aliasing so lets at least make an effort to strive for clean human readable code. So add the lines below to the current file right after the preprocessor library includes.
In order to initialize the boost::asio networking methods we need to create a special object called io_service. The best way to think of io_service is as shared queue which only accepts functions that deal with asynchronous I/O. Thus you can represent a socket bound to a network port within your application and in order to send the socket a method such as connect() the method must get enqueued within the
io_service before its sent down to the operating system.
The documentation on the anatomy of Boost::asio is the most helpful for understanding the architecture of the library. Basic Boost.Asio Anatomy
Add the following function prototypes; we’ll discuss the functions as they get implemented. As you can already guess by the descriptive names of each function the function’s with the suffix Loop will be ran on threads and interact with the messageQueue we defined earlier.
From the explaination earlier in the article we create a
thread_group facilitate the all of our async functions. In regards to the producer-consumer pattern,
inboundLoop() Will push items from the socket to our messageQueue; i.e producer
displayLoop() Removes items from messageQueue to display on the client terminal; i.e consumer
The first function buildPrompt is a function which handles the display of the terminal input for clients.
Its fairly simple in that it takes a string of the clients name and assigns it to the value of the prompt pointer we declared earlier.
Following the buildPrompt() function the first of the threaded functions is the inboundLoop().
Our code for the inboundLoop() is self-explainatory but in particular it creates a loop which only inserts into the thread when a message is available on the socket connected to the server. Reading from the socket object is an operation which may potentially interfere with writing to the socket so we put a one second delay on checks for reading.
As for writting mesasges to the socket to send off to other members of the Chat session we need a loop that will constantly poll for user input. Once the user input is read write the message to the socket wait for the next input. Recall that this operation is threaded so in-comming messages can still be displayed since that happens on an entirely different thread.
For the extra pedantic, you might be wondering why there is no extraneous clean-up code and instead we just call exit(1); for the sake of keeping this tutorial brief and to the point we are not launching a production ready scalable service oriented distributed ChatApplication to be used by thousands of clients. Anyhow moving on the last of the threaded funtions is for displaying the messages read from the socket to the terminal.
The displayLoop() function is fairly crude but it gets the job done. We rely on the fact that every message begins with a user prompt in order to determine if the message belonged to the client or not. When I say crude I mean that a proper chat application with tag each user with a specific id number because our code fails to handle the error when multiple users share the same prompt. Speaking of which here is the last of the utility functions; the one which checks if the prompt from buildPrompt() is found within the string arriving from the socket.
Thanks for reading my tutorial on how to setup a chat client using C++ and the Boost Libraries; this code deserves a refactor considering that many of the Boost code used is now apart of the latest C++ standard. In addition the introduction of a protocol could be useful for unique identification of clients and other things as well.
Stay tuned for the second part of this tutorial where we code the server side of the Chat applciation.