Qt5: Console Applications and Networking

0
10730
QT opener image
Continuing with the series on Qt5 programming, this article takes the reader on to writing code and building a console application, which is also a network server

In the article carried in the February 2015 issue of OSFY, we looked at how Qt makes programming easier by creating a whole new paradigm with extensions to a venerable programming language, and a code generator to help us out. In this article, let’s start writing code, beginning with a console application, which is also a network server.

Figure1
Figure 1: The QtCreator welcome screen
fig 2
Figure 2: The new project dialogue box

Getting started

What we’ll be building is a ‘fortune server’ (the kind of ‘fortune’ you can expect to read in a fortune cookie!), which will select and send a random ‘fortune’ from a set of fortunes every time we connect to it and then disconnect.
Wait a minute, you say. Isn’t Qt for GUI programming? Well, as I’d mentioned in Part 1 of this series of articles, Qt is an application framework, and while it has one of the industry’s best GUI tool kits, that is only a part (albeit a big part) of what Qt does. Qt has an extremely robust network I/O module. We have the freedom to not use the GUI module but instead build a CLI application.
First install the Qt5 Core development packages, a C++ compiler, GNU Make and QtCreator. Figure 1 shows what you should be looking at when you start QtCreator.
Let’s start by creating a project. On the top menu bar, select File->New File Or Project, hit Ctrl+N on the keyboard, or just click the big New Project button on the top left corner of the welcome screen. Either way, you’ll end up looking at the dialogue box shown in Figure 2.

We’re building the server now, so let’s select Applications on the left under Projects, and Qt Console Application in the middle column. Once you’re done, hit Choose. You’ll then end up at the dialogue box shown in Figure 3.

fig 3
Figure 3: New console application

Type in a name (I’ve called it FortuneServer), and hit Next. There’s nothing to do in the Kits screen (just make sure Desktop is ticked), so hit Next again. In the next screen, you can add the project to version control, but since we haven’t set up Git yet, just let that be. Hit Finish.
You should now be staring at the editor with a main.cpp file open. The contents of the file should be:

#include <QCoreApplication>

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
return a.exec();
}

On the top left, you’ll see a list of files that are part of the project. On the bottom left, you’ll see a list of files that are open in the editor. Right now, that should just be main.cpp. You’re now ready to write code.
But before you do, remember that QtCreator projects are structured in a certain way. Every class has its own header file (which contains the class definition) and a .cpp file, which contains code written for the methods. You’ll need to add classes using a wizard.
Hit Ctrl+N on the keyboard. The New Project dialogue box should come up. This time, there will be entries on the bottom-left section for Files and Classes. Select C++ there, and then in the middle column, select C++ Class. Hit Choose.
In the wizard that comes up, give the class a name (I’m calling it FortuneServer again). Use the drop-down menu to select QObject as the base class (this is critical, because, as I learnt the hard way, if you don’t do it here, QtCreator won’t add that file to the list of files needed to be processed by the Meta-Object Compiler (MOC), and then make sure that the Type Information says Inherits Qobject.
Hit Next. Check the summary in the next screen, and hit Finish to create the new class. In the list of open files in the bottom left of QtCreator, you should see two more files–—fortuneserver.cpp and fortuneserver.h. Now you’re ready to fill it with code.
Select fortuneserver.h in the Open Documents section to bring it into the editor. Let’s start defining the class, as follows:

class FortuneServer : public QObject
{
Q_OBJECT

private:

QTcpServer * server;
QList<QByteArray> fortunes;

public:

explicit FortuneServer(QObject *parent = 0);
~FortuneServer();

signals:

private slots:

void sendFortune();
};
fig 4
Figure 4: An empty project in QtCreator
Figure5
Figure 5: New class

This is all pretty standard stuff. The FortuneServer class inherits from QObject and has the Q_OBJECT macro, which sets it up for the MOC. We have a constructor (which needs to take the pointer to a QObject parent to set up the dependency tree) and a destructor. We also have a QList of QByteArrays, which stores all our fortunes. We also have a QTcpServer, which we’ll use to set up the TCP server that handles all those connections that we’ll get.

Notice that we didn’t define any signals. That’s because we don’t need to. We won’t be emitting any signals ourselves, but we do have a private slot, since we’ll be subscribing to the newConnection() signal that will be emitted by the QTcpServer every time there’s a new incoming connection.
Now for the headers; QtCreator will have automatically put a #include <QObject> statement into the file, but we’ll need a few more headers. Here are all the headers we will need:

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHostAddress>
#include <QList>
#include <QByteArray>

That’s right! In Qt5, every component has its own header, so you’ll need to include every one of them manually.
You’ll also need to go ahead and instruct qmake to enable the networking libraries of Qt; so open up the FortuneServer.pro file (it’ll be in the Project pane, on the top-left corner of the screen), and near the top, add the following line…

QT += network

…so that it looks something like what’s shown below:

QT       += core
QT       += network
QT       -= gui

Hit Ctrl+S to save the file. You’ll see a bunch of small progress bars zip by on the bottom-right corner of the screen, as QtCreator takes into account the additional libraries we just enabled and rebuilds the code-completion databases for this project.
We can now start laying down some actual code. Open the fortuneserver.cpp file. By default, it’ll include its own header file (fortuneserver.h) and it’ll also have an empty body for the constructor function.
Let’s fill up that constructor:

FortuneServer::FortuneServer(QObject *parent) : QObject(parent)
{
// first we set up the server and make it listen
// for new connections on 127.0.0.1:56789

server = new QTcpServer(this);
if (!(server->listen(QHostAddress::LocalHost, 56789))) {
qFatal(“ERROR: Failed to bind TCP server to port 56789 on 127.0.0.1”);
}

// now we connect the server’s new connection signal
// to the new connection slot in this object

connect(server, SIGNAL(newConnection()), this, SLOT(sendFortune()));

// let’s populate the fortune list

fortunes.append(QByteArray(“You’ve been leading a dog’s life. Stay off the furniture.\n”));
fortunes.append(QByteArray(“You’ve got to think about tomorrow.\n”));
fortunes.append(QByteArray(“You will be surprised by a loud noise.\n”));
fortunes.append(QByteArray(“You will feel hungry again in another hour.\n”));
fortunes.append(QByteArray(“You might have mail.\n”));
fortunes.append(QByteArray(“You cannot kill time without injuring eternity.\n”));
fortunes.append(QByteArray(“Computers are not intelligent. They only think they are.\n”));

// and we’re done here
}
Figure6
Figure 6: It works

Again, the code should be pretty self-explanatory and the comments should help, but I will mention a few things here and there.
We start by creating a new QTcpServer object, and making it bind to QHostAddress::LocalHost (which is just an alias for 127.0.0.1) and port 56789. It’s a high port so we won’t need root privileges to bind to it. If you want to make an IPv6 server, you can just use the following code: QHostAddress::LocalHostIPv6.
You’ll also notice that the code calls qFatal() with a message if the listen() fails. qFatal() prints the message to stderr and then immediately crashes the program. It doesn’t clean anything up, and lets the operating system deal with it. On Linux, this isn’t a problem, but on other platforms you should do a little housekeeping before you call qFatal().
The next line of code is something you should get familiar with. This is how you connect a signal on some object to a slot. The syntax for the function is:

connect(
pointer_to_object_that_emits_signal,
SIGNAL(signal_name(argument_type, argument_type)),
pointer_to_object_whose_slot_i_want_to_connect_to,
SLOT(slot_name(argument_type, argument_type))
);

It’s important that you wrap the signal in the SIGNAL macro and the slot in the SLOT macro. Also, do not mention any argument names, just the types. You actually emit a signal like this:

emit mySignal(myData);

In this article, we don’t have code that needs to emit a signal, so you won’t be seeing this in action.
Anyway, back to the code—the next thing we do in the constructor is fill up the list of fortunes with a bunch of QByteArrays. We don’t use QStrings here because QTcpSocket’s ‘send’ function works natively with QByteArrays, and QByteArrays can be constructed with standard C-strings; so this makes the code a lot easier. We won’t be able to do fancy text-processing, but we don’t need to. And that’s it for the constructor.
The destructor comprises just the following three lines of actual code:

FortuneServer::~FortuneServer()
{
// shut down the server first

server->close();

// disconnect all signals and slots connected to
// this server

server->disconnect();

// and finally, queue this object for deletion at
// the first opportune moment

server->deleteLater();

// and that’s it
}

I won’t even attempt to explain this one, except to mention that the disconnect() method of any QObject-derived class disconnects all the signals and slots connected to an object of that class.

We don’t delete any QObject-derived object the standard C++ way because there might be pending signals that must be processed by the object. If we delete the object and Qt tries to deliver a signal to it, the program will segfault and make a mess of itself. We call the deleteLater() method (which is actually defined as a slot, and we’ll use that property in the next function we define), and this makes sure there’re no pending tasks for the object before pulling the plug on it.
Now for the one slot we have defined in the class – sendFortune():

void FortuneServer::sendFortune()

{
// we’ll grab a client socket off of the server
// first

QTcpSocket * socket = server->nextPendingConnection();

// now we’ll wait until the socket is connected

if (!(socket->waitForConnected())) {
qDebug() << socket->errorString();
return;
}

// now we’ll choose a random fortune and send it to the
// reciever

socket->write(fortunes.at(qrand() % fortunes.size()));

// we’ll now tear down the connection

socket->disconnectFromHost();
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));

// and we’re done
}

This is also fairly self-explanatory. QTcpServer::nextPendingConnection() returns a pointer to a QTcpSocket (which is like a client socket, if you’ve done BSD socket programming in C). We then wait until the socket is connected, which we don’t necessarily need to, since nextPendingConnection() is supposed to return a connected socket. But in Qt, a QTcpSocket doesn’t emit a signal when it’s ready for us to start writing, so let’s wait a little.

qDebug() works just like std::cout, except that it automatically inserts a new line at the end of every statement; so I don’t have to attach a ‘\n’ or std::endl at the end of every line. Notice that qDebug() isn’t a stream, but rather a stream factory, as we write to the ephemeral object that’s returned by a call to qDebug(). Contrast this with qFatal(), in which we passed the message as an argument. This is because qFatal() never returns – it crashes the program immediately.
In the next few lines, let’s write a random fortune, and then call disconnectFromHost(). Yes, that’s disconnectFromHost(), not disconnect(), because disconnect() on any QObject-derived class (which is pretty much all the classes in Qt) disconnects all signals and slots connected to the object.
Finally, we connect the disconnected() signal (which is emitted when the socket has finally disconnected) to the deleteLater() slot on the same socket. So now, when the socket disconnects, it’ll be queued for deletion. This is the standard way of tearing down a socket.
It’s important that you don’t write() and then immediately close() and deleteLater(). This is because write() and disconnectFromHost() are asynchronous functions, which only perform the writing and disconnecting after the control passes to the main event loop (which happens when our sendFortune() function returns), while close() closes the socket immediately, so the socket will have shut down before sending any data out.
We’ll now have to fill up main.cpp, because we’ll need to initialise a FortuneServer object and run it somewhere. The code snippet for main.cpp, which is very short, is shown below:

#include <QCoreApplication>
#include “fortuneserver.h”

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
FortuneServer f;

return a.exec();
}

And that’s all the coding there is. We’ve just created a network server in about 40 lines of actual code.

Building and running
You can build from QtCreator itself, obviously. On the menu bar, click Build->Build All. You’ll have a progress bar on the bottom-right corner tracking the build, and if there are errors, an Error-messages pane will pop up at the bottom of the window and show you the compiler output.
The actual program will be located at ../build-ProjectName-Desktop-Debug, relative to the project directory where all the source code is. You can just open up a terminal, cd into the directory and type in the name of the executable, and you’re ready to go.
In this case, cd into build-FortuneServer-Desktop-Debug, and type in the following command…

$: ./FortuneServer

…to start the server. Open another terminal tab, and type the following:

$: telnet 127.0.0.1 56789

and press enter. The output I got was:
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is ‘^]’.
You will be surprised by a loud noise.
Connection closed by foreign host

And there it is! ‘You will be surprised by a loud noise’ was my fortune. And it did happen, too, as just a few moments later, my room mate decided to enter the room with a loud push on the door, full-on Kramer-style (you do watch Seinfeld, don’t you?), and I looked up with a start. Freaky!
The qmake way of building the program is pretty easy too. Just open a terminal, cd into the FortuneServer project directory, and type in the following:

$: qmake
$: make

You’ll see some output from g++, and it’ll be done. If you list the files in the directory, you’ll see a makefile that was generated by qmake, and .cpp files whose names start with moc_. These files have been generated by the MOC, so if you want to know what goes on behind the scenes with Qt, you can just go ahead and look at those files now. And the compiled executable is sitting in the same directory, so go ahead and run it!

What now?
First of all, if you need access to the complete code, it’s available online on my GitHub account, at https://github.com/BaloneyGeek/FortuneServerExample. It builds, so just clone and qmake && make to build.
In the next (and final) article in the series, we’ll try out something that’s fun and build a small application that fetches a fortune and displays it on the screen—in a shiny new GUI.

LEAVE A REPLY

Please enter your comment!
Please enter your name here