Basic network access

Nowadays network connectivity is taken as granted in almost all applications (running in both mobile and non-mobile devices). So maybe I should also take a look at how to add network access support to my Qt-based apps. I think I will start with a very basic client/server system that will communicate over the PC loopback interface. The loopback interface basically allows me to talk to myself over the network. However, using the loopback interface does not restrict the application in any way because the same functions can be used to access any remote system (either in the local network or in the internet). It is just the easiest way to start because I don’t need to worry about firewalls, routers, etc. that typically need to be reconfigured when the client and server are running on different host systems.

The figure shows the network environment in a typical home network. The devices are connected to each other over the LAN (Local Area Network) or over WLAN (Wireless Local Area Network). The local network is isolated from the internet with a router. The router has a firewall that typically blocks all incoming connection attempts. The router also does network address translation (NAT) creating a local IP-address space for the home network.

Each host in the network is identified by an IP-address. Currently we have two addressing schemes in use: IPv4 and IPv6. IPv4 address consists of four 8-bit numbers (octets). The loop-back interface address is 127.0.0.1 (it is also identified by the name localhost). To address a specific server running on a specific host we also need a port number. On linux systems port numbers starting from 1024 can be used when running the server as an ordinary user.

I’m going to create two console application projects: Server and Client. The server will be started first. It will listen for connections at the loopback interface at port 1025. When the client is started it will try to contact the server and then it will try to talk to the server by sending and receiving some simple messages.

Server

The Server project file (Server.pro) is shown below. We need to include the network unit to the QT variable. Otherwise this is just a basic console application project definition.


QT += core network

QT -= gui

TARGET = Server
CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp \
server.cpp

HEADERS += \
server.h

The server functionality is defined in the class Server (in the files server.h and server.cpp). The server.h contains the class declaration.


#ifndef SERVER_H
#define SERVER_H

#include <QObject>
#include <QDebug>
#include <QtNetwork/QTcpSocket>
#include <QTcpServer>

class Server : public QObject
{
Q_OBJECT

QTcpServer *tcpServer;
QTcpSocket *socket;

public:
explicit Server();

public slots:
void acceptConnection();
void receiveMessage();
};

#endif // SERVER_H

In the server.cpp we will implement the slots and connect the slots to the correct signals in the QTcpServer and QTcpSocket objects. The QTcpServer will send the signal newConnection when a client tries to connect. The signal is connected to the acceptConnection slot, which will create a socket for data transmission. The QTcpSocket will send the signal readyRead when the client sends some data. The signal is connected to the slot receiveMessage that will read the data from the socket.


#include "server.h"

Server::Server()
{
tcpServer = new QTcpServer();

if(tcpServer->listen(QHostAddress::LocalHost,1025))
{
qDebug() << "- server up.";
qDebug() << "- address: " << tcpServer->serverAddress();
qDebug() << "- port: " << tcpServer->serverPort();
QObject::connect(tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
}
else
{
qDebug() << "Failed to start the server:" << tcpServer->serverError();
exit(EXIT_FAILURE);
}
}

void Server::acceptConnection()
{
qDebug() << "- accept client connection.";

socket = tcpServer->nextPendingConnection();
connect(socket,SIGNAL(disconnected()), socket, SLOT(deleteLater()));
connect(socket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));

qDebug() << "- greet the client.";
QByteArray message = QString("Hello, client!").toLocal8Bit().constData();
socket->write(message);
}

void Server::receiveMessage()
{

QByteArray message = socket->readLine();
qDebug() << "- message received from client.";
qDebug() << message.constData();

// Acknowledge
message = QString("Thanks for the message, goodbye!").toLocal8Bit().constData();
socket->write(message);
socket->disconnectFromHost();

}

The main function just instantiates the Server class.

#include <QtCore/QCoreApplication>
#include "server.h"

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

    new Server;

    return a.exec();
}

Client

The Client project file (Client.pro) is similar to the server one.

QT       += core network

QT       -= gui

TARGET = Client
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp \
    client.cpp

HEADERS += \
    client.h

The client functionality is defined in the class Client (in the files client.h and client.cpp). The client.h contains the class declaration. We need one QTcpSocket to start talking to the server.

#ifndef CLIENT_H
#define CLIENT_H

#include <QObject>
#include <QAbstractSocket>
#include <QtNetwork/QTcpSocket>
#include <QHostAddress>

class Client : public QObject
{
    Q_OBJECT
    QTcpSocket *socket;
    bool startingConversation;

public:
    explicit Client();
    
signals:
    
public slots:
    void receiveMessage();
    void connected();
    void errorOccured(QAbstractSocket::SocketError errorMsg);
};

#endif // CLIENT_H

In the client.cpp we will implement the slots and connect the slots to the correct signals in the QTcpSocket object. The socket will send the signal connected when the server responds to our connection attempt. The signal readyRead is sent when the server has sent some data. It is connected to the receiveMessage slot. The socket will send the error signal if the server cannot be contacted.

#include <QDateTime>
#include "client.h"

Client::Client()
{
    socket = new QTcpSocket();
    socket->connect(socket,SIGNAL(connected()),this,SLOT(connected()));
    socket->connect(socket,SIGNAL(readyRead()),this,SLOT(receiveMessage()));
    socket->connect(socket,SIGNAL(error(QAbstractSocket::SocketError)),
                    this,SLOT(errorOccured(QAbstractSocket::SocketError)));

    qDebug() << "- client running, connecting to the server.";

    socket->connectToHost(QHostAddress::LocalHost,1025);
    startingConversation = true;
}

void Client::receiveMessage()
{
    qDebug() << "- message received from server.";
    QByteArray message = socket->readLine();
    qDebug() << message.constData();

    if(startingConversation)
    {
        qDebug() << "- reply to server.";
        message = QString("Hello, server!").toLocal8Bit().constData();
        socket->write(message);
        startingConversation=false;
    }

}


void Client::connected()
{
    qDebug() << "- connection successful.";
}

void Client::errorOccured(QAbstractSocket::SocketError errorMsg)
{
    if(errorMsg==QAbstractSocket::RemoteHostClosedError)
    {
        qDebug() << "- server closed the connection, exiting...";
        exit(EXIT_SUCCESS);
    }
    else
        qDebug() << "Error: " << errorMsg;
}

And finally the main function will just instantiate the Client class.

#include <QtCore/QCoreApplication>
#include "client.h"


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

    new Client;

    return a.exec();
}

Testing

Let’s first build and run the server. It will start the QTcpServer, which will just wait for connection attempts at port 1025 in the loopback interface. Next we can build and run the client.

Getting the source code

The source code is available at Github:

https://github.com/n9dyfi/Basic-Client-Server