WIP-How to create a simple chat application
Introduction
This article will illustrate a simple chat client and server communicating over TCP. The aim is to clarify aspects of QTcpSocket/QTcpServer that are not developed in the official Qt Fortune example. This has no intention to be a fully featured chat application.
You can find the source code relating to this example on GitHub
Pre-Requisites
- Qt 5.7 or later
- An intermediate level of knowledge of Qt5
- Familiarity with C++11 concepts, especially lambdas and std::bind
The Logic
This application will use a central server that will manage the communication among clients via JSON messages. We'll implement 2 versions of the server, one that runs in a single server and one that distributes the sockets among multiple threads.
The Client
The client in this example is a simple QtWidgets application, the ui is minimal, just a button to connect to the server, a list view to display the messages received, a line edit to type your messages and a button to send them.
The core of the functionality is in the ChatClient class.
class ChatClient : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(ChatClient)
public:
explicit ChatClient(QObject *parent = nullptr);
public slots:
void connectToServer(const QHostAddress &address, quint16 port);
void login(const QString &userName);
void sendMessage(const QString &text);
void disconnectFromHost();
private slots:
void onReadyRead();
signals:
void connected();
void loggedIn();
void loginError(const QString &reason);
void disconnected();
void messageReceived(const QString &sender, const QString &text);
void error(QAbstractSocket::SocketError socketError);
void userJoined(const QString &username);
void userLeft(const QString &username);
private:
QTcpSocket *m_clientSocket;
bool m_loggedIn;
void jsonReceived(const QJsonObject &doc);
};
Let's break down what each of these methods and member does:
Private Members | |
---|---|
m_clientSocket | The socket that will interact with the server |
m_loggedIn | Used to record whether the client already agreed a username with the server or not |
Private Methods and Slots | |
onReadyRead() | Slot that reacts to data being available on the socket. It will download the data and pass it to jsonReceived() |
jsonReceived() | Method to process the messages received from the server |
Signals | |
connected() | The client connected to the server successfully |
disconnected() | The client is not connected any more |
loggedIn() | The client successfully agreed a username with the server |
loginError() | The server rejected the username provided |
messageReceived() | The client received a chat message |
userJoined() | A new user joined the chat |
userLeft() | A user left the chat |
error() | Just a forward of the QTcpSocket::error signal |
Public Slots | |
connectToServer() | Attempts the connection to a server |
login() | Attempts to agree the given username with the server |
disconnectFromHost() | Closes the connection with the server |
sendMessage() | Sends a chat message to the server |
Let's now look at the implementation:
ChatClient::ChatClient(QObject *parent)
: QObject(parent)
, m_clientSocket(new QTcpSocket(this))
, m_loggedIn(false)
{
In the constructor we initialize the parent class and the members, then we take care of connecting to the signals coming from the socket.
// Forward the connected and disconnected signals
connect(m_clientSocket, &QTcpSocket::connected, this, &ChatClient::connected);
connect(m_clientSocket, &QTcpSocket::disconnected, this, &ChatClient::disconnected);
// connect readyRead() to the slot that will take care of reading the data in
connect(m_clientSocket, &QTcpSocket::readyRead, this, &ChatClient::onReadyRead);
// Forward the error signal, QOverload is necessary as error() is overloaded, see the Qt docs
connect(m_clientSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &ChatClient::error);
// Reset the m_loggedIn variable when we disconnec. Since the operation is trivial we use a lambda instead of creating another slot
connect(m_clientSocket, &QTcpSocket::disconnected, this, [this]()->void{m_loggedIn = false;});