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, lets start writing code, beginning with a console application, which is also a network server.
Getting started
What well 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. Isnt Qt for GUI programming? Well, as Id mentioned in Part 1 of this series of articles, Qt is an application framework, and while it has one of the industrys 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.
Lets 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, youll end up looking at the dialogue box shown in Figure 2.
Were building the server now, so lets select Applications on the left under Projects, and Qt Console Application in the middle column. Once youre done, hit Choose. Youll then end up at the dialogue box shown in Figure 3.
Type in a name (Ive called it FortuneServer), and hit Next. Theres 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 havent 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, youll see a list of files that are part of the project. On the bottom left, youll see a list of files that are open in the editor. Right now, that should just be main.cpp. Youre 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. Youll 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 (Im 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 dont do it here, QtCreator wont 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 youre ready to fill it with code.
Select fortuneserver.h in the Open Documents section to bring it into the editor. Lets 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(); };
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 well use to set up the TCP server that handles all those connections that well get.
Notice that we didnt define any signals. Thats because we dont need to. We wont be emitting any signals ourselves, but we do have a private slot, since well be subscribing to the newConnection() signal that will be emitted by the QTcpServer every time theres a new incoming connection.
Now for the headers; QtCreator will have automatically put a #include <QObject> statement into the file, but well 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>
Thats right! In Qt5, every component has its own header, so youll need to include every one of them manually.
Youll also need to go ahead and instruct qmake to enable the networking libraries of Qt; so open up the FortuneServer.pro file (itll 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 whats shown below:
QT += core QT += network QT -= gui
Hit Ctrl+S to save the file. Youll 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, itll include its own header file (fortuneserver.h) and itll also have an empty body for the constructor function.
Lets 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 servers new connection signal // to the new connection slot in this object connect(server, SIGNAL(newConnection()), this, SLOT(sendFortune())); // lets populate the fortune list fortunes.append(QByteArray(Youve been leading a dogs life. Stay off the furniture.\n)); fortunes.append(QByteArray(Youve 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 were done here }
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. Its a high port so we wont need root privileges to bind to it. If you want to make an IPv6 server, you can just use the following code: QHostAddress::LocalHostIPv6.
Youll 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 doesnt clean anything up, and lets the operating system deal with it. On Linux, this isnt 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)) );
Its 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 dont have code that needs to emit a signal, so you wont be seeing this in action.
Anyway, back to the codethe next thing we do in the constructor is fill up the list of fortunes with a bunch of QByteArrays. We dont use QStrings here because QTcpSockets send function works natively with QByteArrays, and QByteArrays can be constructed with standard C-strings; so this makes the code a lot easier. We wont be able to do fancy text-processing, but we dont need to. And thats 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 thats it }
I wont 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 dont 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 well use that property in the next function we define), and this makes sure therere 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() { // well grab a client socket off of the server // first QTcpSocket * socket = server->nextPendingConnection(); // now well wait until the socket is connected if (!(socket->waitForConnected())) { qDebug() << socket->errorString(); return; } // now well choose a random fortune and send it to the // reciever socket->write(fortunes.at(qrand() % fortunes.size())); // well now tear down the connection socket->disconnectFromHost(); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); // and were done }
This is also fairly self-explanatory. QTcpServer::nextPendingConnection() returns a pointer to a QTcpSocket (which is like a client socket, if youve done BSD socket programming in C). We then wait until the socket is connected, which we dont necessarily need to, since nextPendingConnection() is supposed to return a connected socket. But in Qt, a QTcpSocket doesnt emit a signal when its ready for us to start writing, so lets 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 dont have to attach a \n or std::endl at the end of every line. Notice that qDebug() isnt a stream, but rather a stream factory, as we write to the ephemeral object thats 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, lets write a random fortune, and then call disconnectFromHost(). Yes, thats 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, itll be queued for deletion. This is the standard way of tearing down a socket.
Its important that you dont 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.
Well now have to fill up main.cpp, because well 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 thats all the coding there is. Weve 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. Youll 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 youre 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, dont 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
Youll see some output from g++, and itll be done. If you list the files in the directory, youll 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, its 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, well try out something thats fun and build a small application that fetches a fortune and displays it on the screenin a shiny new GUI.