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
The Logic
This application will use a central server that will manage the communication among clients via JSON messages. On the server side, we'll distribute the clients over multiple threads to speed up the processing.
The Server Socket
The fist class we'll look at is the socket on the server that will communicate with a single client
#include <QTcpSocket>
class ServerSocket : public QTcpSocket
{
Q_OBJECT
Q_DISABLE_COPY(ServerSocket)
public:
explicit ServerSocket(QObject* parent = nullptr);
private slots:
void receiveJson();
void sendJson(const QByteArray& jsonData);
signals:
void jsonReceived(const QJsonDocument& jsonDoc);
};
The declaration is very simple, we are just adding 2 slots: sendJson to send messages to the client and receiveJson to receive and decode a message coming from the client. The jsonReceived signal will notify the server of incoming data.
#include "serversocket.h"
#include <QDataStream>
#include <QJsonDocument>
#include <QJsonParseError>
ServerSocket::ServerSocket(QObject* parent)
:QTcpSocket(parent)
{
connect(this,&ServerSocket::readyRead,this,&ServerSocket::receiveJson);
}
void ServerSocket::sendJson(const QByteArray &jsonData)
{
QDataStream socketStream(this);
socketStream.setVersion(QDataStream::Qt_5_6);
socketStream << jsonData;
}
void ServerSocket::receiveJson()
{
QByteArray jsonData;
QDataStream socketStream(this);
socketStream.setVersion(QDataStream::Qt_5_6);
for(;;){
socketStream.startTransaction();
socketStream >> jsonData;
if(socketStream.commitTransaction()){
QJsonParseError parseError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData,&parseError);
if(parseError.error == QJsonParseError::NoError)
emit jsonReceived(jsonDoc);
}
else{
break;
}
}
}
The implementation is also relatively straightforward. The constructor calls the base class and the connects the readyRead signal to receiveJson slot that will take care of decoding the data. sendJson will just write the data to the socket. socketStream.setVersion(QDataStream::Qt_5_6); makes sure that clients compiled with different versions of Qt all communicate in the same way. receiveJson is just slightly more involved: since readyRead is emitted when there is some data available to read but not necessarily all of it, we start a transaction. We then start an infinite loop that keeps trying to read JSON data. If the data was read correctly, commitTransaction will return true and we proceed with parsing the JSON into a QJsonDocument otherwise we just stop and wait for more data to arrive.