📜 ⬆️ ⬇️

Working with sockets in Qt

Introduction


image
Some years ago, on one of the forums, I found such a wonderful phrase - “Every self-respecting programmer in life should write his chat client.” Then my knowledge did not allow to do this. I just smiled and passed this phrase. But just recently I ran into this particular problem - I had to write my own chat. Well, since lately my interest was directed to the study and development of Qt-applications, on which it will be made, it was decided by itself.

Working with the network in Qt is done through QtNetwork. And in order for the project to start supporting it, in the .pro file you need to add QT + = network. Qt supports several types of socket connections. The main ones are: QUdpSocket and QTcpSocket. QUdpSocket is a datagram socket for exchanging data packets. With this socket, the data is sent without checking whether the data has reached or not. QTcpSocket, on the other hand, establishes a point-to-point connection and provides additional mechanisms against anti-corruption and data loss.

Both classes are inherited from the QAbstractSocket class. There is also a QSslSocket class, but I was not needed at that time, so I will not touch it.

Chat


When I started designing the application architecture, the question became: “How will the chat users interact with each other?”. Just the chat itself had to support both the general chat window and private messages with the ability to transfer files. If everything was clear with the transfer of files - there is only TCP, since there should be control and check whether packets came, then there were difficulties with the main chat. After all, in the chat there is no clearly defined server. And if their interaction is done via TCP, then for each user you will need to select a port, which is a little expensive. Well, if not TCP, then UDP! It is completely suitable for this. Listening to a specific port, we receive messages from the user who sent them to the same port for everyone. And if some chat message is lost, it will not be so scary.
')

QUdpSocket


As it turned out, working with the QUdpSocket class is not easy, but very simple. We create a class object, bind the specified port, connect it with the QIODevice class signal and wait for messages to arrive at this port.
UdpChat(QString nick, int port) {
nickname = nick;
socket = new QUdpSocket( this );
_port = port;
socket->bind(QHostAddress::Any, port);
// //
connect(socket, SIGNAL(readyRead()), SLOT(read()));
}

* This source code was highlighted with Source Code Highlighter .

I would also like to say before the next block of code that in our case the chat does not know the number of people on the network. We will just make a broadcast request. But in order to create a list of people, we will send something like “Who is online?”, To which those who receive this message will send “I!”. Thus, we will have 3 message types:
  1. regular message from user
  2. messages - online user
  3. message - who is online?

As a result, we obtain:
void send(QString str, qint8 type) {
QByteArray data;
QDataStream out (&data, QIODevice::WriteOnly);
out << qint64(0);
out << qint8(type);
out << str;
out .device()->seek(qint64(0));
out << qint64(data.size() - sizeof (qint64));
socket->writeDatagram(data, QHostAddress::Broadcast, _port);
}

void read() {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());
QHostAddress *address = new QHostAddress();
socket->readDatagram(datagram.data(), datagram.size(), address);

QDataStream in (&datagram, QIODevice::ReadOnly);

qint64 size = -1;
if ( in .device()->size() > sizeof (qint64)) {
in >> size;
} else return ;
if ( in .device()->size() - sizeof (qint64) < size) return ;

qint8 type = 0;
in >> type;

if (type == USUAL_MESSAGE) {
QString str;
in >> str;
// //
} else if (type == PERSON_ONLINE) {
// QHostAddress //
} else if (type == WHO_IS_ONLINE) {
sending(nickname, qint8(PERSON_ONLINE));
}
}


* This source code was highlighted with Source Code Highlighter .

Everything is transparent and clear. It remains to fasten the interface, connect all the signals and the main chat window is ready.

QTcpSocket


As for this socket, it implements the client-server model. That is, in our case, each user can become as a server if he starts someone first to write a private message or send a file. Or the client, if he wrote the first in private.

There are no difficulties with sending a message. But to send the file we do a classic trick. Since the whole file cannot be inserted into the stream, it will read the data from the file and write it in portions to the stream, and then send it.
void sendFile(QString fileName) {
if (sendFile != NULL){
return ;
}
sendFile = new QFile(fileName);
if (sendFile->open(QFile::ReadOnly)){
QByteArray data;
QDataStream out (&data, QIODevice::WriteOnly);
// //
clientSocket->write(data);
clientSocket->waitForBytesWritten();
connect(clientSocket, SIGNAL(bytesWritten(qint64)), this , SLOT(sendPartOfFile()));
sendPartOfFile();
} else {
emit this ->errorSendFile(QString( "File not can open for read" ));
return ;
}
}

void sendPartOfFile() {
char block[SIZE_BLOCK_FOR_SEND_FILE];
if (!sendFile->atEnd()){
qint64 in = sendFile->read(block, sizeof (block));
qint64 send = clientSocket->write(block, in );
} else {
sendFile->close();
sendFile = NULL;
disconnect(clientSocket, SIGNAL(bytesWritten(qint64)), this , SLOT(sendPartOfFile()));
emit endSendFile();
}
}


* This source code was highlighted with Source Code Highlighter .

It remains to consider reading the stream. Since only 2 types of data come in - MESSAGE and SENDFILE, then it’s easy to write a regular input stream parser. It is identical to the class method implementing a UDP socket. Of interest is reading from the file stream.
void receiveFile(QString fileName) {
QString savePath = "Downloads/" ;
QDir dir;
dir.mkpath(savePath);
receiveFile = new QFile(savePath + fileName);
sizeReceivedData = 0;
receiveFile();
}

void receiveFile() {
QDataStream in (clientSocket);
if (!bufferForUnreadData.isEmpty()){
receiveFile->write(bufferForUnreadData);
sizeReceivedData += bufferForUnreadData.size();
bufferForUnreadData.clear();
}
char block[SIZE_BLOCK_FOR_SEND_FILE];
while (! in .atEnd()){
qint64 toFile = in .readRawData(block, sizeof (block));
sizeReceivedData += toFile;
receiveFile->write(block, toFile);
}
if (sizeReceivedData == sizeReceiveFile){
receiveFile->close();
receiveFile = NULL;
sizeReceiveFile = 0;
sizeReceivedData = 0;
}
}

* This source code was highlighted with Source Code Highlighter .

This is the essence of the work of private messages. Of course, there is a shortage of signals, connections and specific code. For example, a send and recieve file is only a general view. Much has been missed. But if you add everything, fasten the interface, you can get quite a working chat client written by you.

The purpose of the article was to show by example that working with sockets is simple and easy. I hope that this article will help someone in the future. Good luck.

Source: https://habr.com/ru/post/111239/


All Articles